17#ifndef ALICEO2_CADSUPPORT_BOUNDEDSURFACE_H_
18#define ALICEO2_CADSUPPORT_BOUNDEDSURFACE_H_
90 return {vector.xCoord * scale, vector.yCoord * scale, vector.zCoord * scale};
95 return vector * scale;
143 const double trace = gUU + gVV;
144 const double determinant = gUU * gVV - gUV * gUV;
146 const double discriminant = std::max(0., trace * trace - 4. * determinant);
147 return std::sqrt(std::max(0., 0.5 * (trace + std::sqrt(discriminant))));
153template <
typename Surface>
156 return {[](
const void* context,
const Vec2& uv,
double& gUU,
double& gUV,
double& gVV) {
157 static_cast<const Surface*
>(context)->parametricMetric(uv, gUU, gUV, gVV);
162inline double dot(
const Vec3& firstVector,
const Vec3& secondVector)
177 return dot(vector, vector);
182 return std::sqrt(
normSq(vector));
187 const double vectorNorm =
norm(vector);
191 return vector * (1. / vectorNorm);
196 if (dimension == 0) {
197 return vector.xCoord;
199 if (dimension == 1) {
200 return vector.yCoord;
202 return vector.zCoord;
207 if (dimension == 0) {
208 vector.xCoord =
value;
209 }
else if (dimension == 1) {
210 vector.yCoord =
value;
212 vector.zCoord =
value;
218 return std::isfinite(point.
uCoord) && std::isfinite(point.
vCoord);
223 return std::isfinite(point.
xCoord) && std::isfinite(point.
yCoord) && std::isfinite(point.
zCoord);
228 const double deltaU = firstPoint.
uCoord - secondPoint.
uCoord;
229 const double deltaV = firstPoint.
vCoord - secondPoint.
vCoord;
230 return deltaU * deltaU + deltaV * deltaV;
235 return normSq(firstPoint - secondPoint);
245 const Vec2 segmentVector = segmentEnd - segmentStart;
246 const double segmentLengthSq = segmentVector.
uCoord * segmentVector.
uCoord + segmentVector.
vCoord * segmentVector.
vCoord;
250 const double pointProjection = ((point.
uCoord - segmentStart.
uCoord) * segmentVector.
uCoord +
253 const double clampedProjection = std::max(0., std::min(1., pointProjection));
254 const Vec2 closestPoint{segmentStart.
uCoord + clampedProjection * segmentVector.
uCoord,
255 segmentStart.
vCoord + clampedProjection * segmentVector.
vCoord};
261 const Vec3 segmentVector = segmentEnd - segmentStart;
262 const double segmentLengthSq =
normSq(segmentVector);
266 const double pointProjection =
dot(point - segmentStart, segmentVector) / segmentLengthSq;
267 const double clampedProjection = std::max(0., std::min(1., pointProjection));
268 const Vec3 closestPoint = segmentStart + segmentVector * clampedProjection;
279 gUU =
dot(axisU, axisU);
280 gUV =
dot(axisU, axisV);
281 gVV =
dot(axisV, axisV);
287 gUU = radius * radius;
296 gUU = radiusAtHeight * radiusAtHeight;
305 const double parallelRadius = radius * std::sin(theta);
306 gUU = parallelRadius * parallelRadius;
308 gVV = radius * radius;
315 const double ringRadius = majorRadius + minorRadius * std::cos(phiTube);
316 gUU = ringRadius * ringRadius;
318 gVV = minorRadius * minorRadius;
324 return std::abs(firstDistance - secondDistance) <=
359 const double segmentLengthSq = segmentVector.
uCoord * segmentVector.
uCoord +
365 const double projection = ((point.
uCoord -
start.uCoord) * segmentVector.
uCoord +
368 parameter = std::max(0., std::min(1., projection));
369 return {
start.uCoord + parameter * segmentVector.
uCoord,
start.vCoord + parameter * segmentVector.
vCoord};
411 return "orientation normalized to match wire role";
413 return "wire contains a non-finite vertex";
415 return "wire edges do not form a closed loop";
417 return "wire needs at least three distinct vertices";
419 return "wire has a coincident (pinched) vertex";
421 return "wire has zero area";
423 return "unknown wire status";
429 const double scale = metric.
maxScale(uv);
448 return static_cast<int>(
index);
466 vertices.reserve(inputVertices.size());
467 bool droppedAVertex =
false;
469 for (
const auto&
vertex : inputVertices) {
477 droppedAVertex =
true;
484 droppedAVertex =
true;
493 for (
size_t firstIndex = 0; firstIndex <
vertices.size(); ++firstIndex) {
494 for (
size_t secondIndex = firstIndex + 1; secondIndex <
vertices.size(); ++secondIndex) {
509 const int storedCount =
static_cast<int>(
vertices.size());
510 sourceEdge.assign(
static_cast<size_t>(storedCount), -1);
511 if (!droppedAVertex) {
519 if ((area > 0.) != wantPositiveArea) {
523 std::vector<int> reversedSource(
static_cast<size_t>(storedCount), -1);
525 reversedSource[
static_cast<size_t>(
index)] =
526 sourceEdge[
static_cast<size_t>((storedCount - 2 -
index % storedCount + 2 * storedCount) % storedCount)];
541 if (
edges.size() < 3) {
545 for (
size_t edgeIndex = 0; edgeIndex <
edges.size(); ++edgeIndex) {
550 const Vec2& nextStart =
edges[(edgeIndex + 1) %
edges.size()].start;
551 if (metric.distanceSq(
edges[edgeIndex].end, nextStart) > joinTolerance * joinTolerance) {
557 std::vector<Vec2> ringVertices;
558 ringVertices.reserve(
edges.size());
559 for (
const auto& singleEdge :
edges) {
560 ringVertices.push_back(singleEdge.start);
562 return initialize(ringVertices, wireRole, status, metric);
568 for (
size_t vertexIndex = 0; vertexIndex <
vertices.size(); ++vertexIndex) {
569 const auto& currentVertex =
vertices[vertexIndex];
571 area += currentVertex.uCoord * nextVertex.vCoord - nextVertex.uCoord * currentVertex.vCoord;
604 const double bandSq = band * band;
606 for (
size_t vertexIndex = 0; vertexIndex <
vertices.size(); ++vertexIndex) {
607 const auto& segmentStart =
vertices[vertexIndex];
612 const bool crossesScanline = (segmentStart.vCoord > point.
vCoord) != (segmentEnd.vCoord > point.
vCoord);
613 if (crossesScanline) {
614 const double intersectionU = segmentStart.uCoord + (point.
vCoord - segmentStart.vCoord) *
615 (segmentEnd.uCoord - segmentStart.uCoord) /
616 (segmentEnd.vCoord - segmentStart.vCoord);
617 if (point.
uCoord < intersectionU) {
633 const Vec2& thirdVertex)
635 const double firstCross =
cross2D(secondVertex - firstVertex, point - firstVertex);
636 const double secondCross =
cross2D(thirdVertex - secondVertex, point - secondVertex);
637 const double thirdCross =
cross2D(firstVertex - thirdVertex, point - thirdVertex);
644 std::vector<int> remainingIndices;
645 remainingIndices.reserve(wire.
vertices.size());
647 for (
size_t vertexIndex = 0; vertexIndex < wire.
vertices.size(); ++vertexIndex) {
648 remainingIndices.push_back(
static_cast<int>(vertexIndex));
651 for (
size_t reverseIndex = wire.
vertices.size(); reverseIndex > 0; --reverseIndex) {
652 remainingIndices.push_back(
static_cast<int>(reverseIndex - 1));
656 std::vector<std::array<int, 3>> triangles;
657 size_t guardCounter = 0;
658 while (remainingIndices.size() > 3 && guardCounter++ < wire.
vertices.size() * wire.
vertices.size()) {
659 bool clippedEar =
false;
660 for (
size_t indexPosition = 0; indexPosition < remainingIndices.size(); ++indexPosition) {
661 const int previousIndex = remainingIndices[(indexPosition + remainingIndices.size() - 1) % remainingIndices.size()];
662 const int currentIndex = remainingIndices[indexPosition];
663 const int nextIndex = remainingIndices[(indexPosition + 1) % remainingIndices.size()];
665 const auto& previousVertex = wire.
vertices[previousIndex];
666 const auto& currentVertex = wire.
vertices[currentIndex];
667 const auto& nextVertex = wire.
vertices[nextIndex];
668 if (
cross2D(currentVertex - previousVertex, nextVertex - currentVertex) <=
kTolerance) {
672 bool containsOtherVertex =
false;
673 for (
int candidateIndex : remainingIndices) {
674 if (candidateIndex == previousIndex || candidateIndex == currentIndex || candidateIndex == nextIndex) {
678 containsOtherVertex =
true;
682 if (containsOtherVertex) {
686 triangles.push_back({previousIndex, currentIndex, nextIndex});
687 remainingIndices.erase(remainingIndices.begin() + indexPosition);
697 if (remainingIndices.size() == 3) {
698 triangles.push_back({remainingIndices[0], remainingIndices[1], remainingIndices[2]});
705inline constexpr double kPi = 3.14159265358979323846;
726 return std::max(1, std::min(fullTurnChunks,
static_cast<int>(std::ceil(span /
kCoverChunkAngle))));
732 const double atStart =
a * std::cos(
t0) +
b * std::sin(
t0);
733 const double atEnd =
a * std::cos(
t1) +
b * std::sin(
t1);
734 minimum = std::min(atStart, atEnd);
735 maximum = std::max(atStart, atEnd);
736 const double amplitude = std::hypot(
a,
b);
737 const double crest = std::atan2(
b,
a);
740 const double crestInRange = crest -
kTwoPi * std::floor((crest -
t0) /
kTwoPi);
741 if (crestInRange <=
t1) {
744 const double trough = crest +
kPi;
745 const double troughInRange = trough -
kTwoPi * std::floor((trough -
t0) /
kTwoPi);
746 if (troughInRange <=
t1) {
747 minimum = -amplitude;
779 return delta <= sweep + tolerance || delta >=
kTwoPi - tolerance;
785 nodes.assign(std::max(
n, 1), 0.);
790 for (
int i = 0;
i <
n; ++
i) {
791 double root = std::cos(
kPi * (
i + 0.75) / (
n + 0.5));
792 double derivative = 1.;
793 for (
int iteration = 0; iteration < 100; ++iteration) {
794 double previous = 1.;
795 double current = root;
796 for (
int degreeIndex = 2; degreeIndex <=
n; ++degreeIndex) {
797 const double next = ((2 * degreeIndex - 1) * root * current - (degreeIndex - 1) * previous) / degreeIndex;
801 derivative =
n * (root * current - previous) / (root * root - 1.);
802 const double delta = current / derivative;
804 if (std::abs(delta) < 1.e-15) {
809 weights[
i] = 2. / ((1. - root * root) * derivative * derivative);
817 const double discriminant = coeffQ * coeffQ / 4. + coeffP * coeffP * coeffP / 27.;
818 if (!(coeffP < 0.) || discriminant > 0.) {
819 const double sqrtDiscriminant = std::sqrt(std::max(0., discriminant));
820 roots[0] = std::cbrt(-0.5 * coeffQ + sqrtDiscriminant) + std::cbrt(-0.5 * coeffQ - sqrtDiscriminant);
824 const double magnitude = 2. * std::sqrt(-coeffP / 3.);
825 const double cosineArgument = std::max(-1., std::min(1., 3. * coeffQ / (coeffP * magnitude)));
826 const double baseAngle = std::acos(cosineArgument);
827 for (
int branch = 0; branch < 3; ++branch) {
828 roots[branch] = magnitude * std::cos((baseAngle -
kTwoPi * branch) / 3.);
846 assert(
count < 4 &&
"QuarticRoots holds at most four roots");
853 size_t size()
const {
return static_cast<size_t>(
count); }
865 *takenBranch = branch;
872 if (!(std::abs(a4) > 0.)) {
876 double coeffB = a3 / a4, coeffC = a2 / a4, coeffD = a1 / a4, coeffE = a0 / a4;
877 if (!std::isfinite(coeffB) || !std::isfinite(coeffC) || !std::isfinite(coeffD) || !std::isfinite(coeffE)) {
881 const double rootBound = std::max({std::abs(coeffB), std::sqrt(std::abs(coeffC)),
882 std::cbrt(std::abs(coeffD)), std::sqrt(std::sqrt(std::abs(coeffE)))});
883 int boundExponent = 0;
884 std::frexp(rootBound, &boundExponent);
885 const double scale = std::ldexp(1., boundExponent);
887 coeffC /= scale * scale;
888 coeffD /= scale * scale * scale;
889 coeffE /= scale * scale * scale * scale;
892 const double termP = coeffC - 3. * coeffB * coeffB / 8.;
893 const double termQ = coeffD - coeffB * coeffC / 2. + coeffB * coeffB * coeffB / 8.;
895 coeffE - coeffB * coeffD / 4. + coeffB * coeffB * coeffC / 16. - 3. * coeffB * coeffB * coeffB * coeffB / 256.;
896 const double shift = -coeffB / 4.;
898 auto addQuadraticRoots = [&](
double quadB,
double quadC) {
899 const double discriminant = quadB * quadB - 4. * quadC;
900 if (discriminant < 0.) {
903 const double sqrtDiscriminant = std::sqrt(discriminant);
904 roots.
push_back(shift + 0.5 * (-quadB - sqrtDiscriminant));
905 roots.
push_back(shift + 0.5 * (-quadB + sqrtDiscriminant));
908 auto addBiquadraticRoots = [&]() {
910 const double discriminant = termP * termP - 4. * termR;
911 if (discriminant < 0.) {
914 const double sqrtDiscriminant = std::sqrt(discriminant);
915 for (
const double zSquared : {0.5 * (-termP + sqrtDiscriminant), 0.5 * (-termP - sqrtDiscriminant)}) {
916 if (zSquared >= 0.) {
917 const double z = std::sqrt(zSquared);
929 const double cubicA2 = termP;
930 const double cubicA1 = termP * termP / 4. - termR;
931 const double cubicA0 = -termQ * termQ / 8.;
932 const double cubicP = cubicA1 - cubicA2 * cubicA2 / 3.;
933 const double cubicQ = 2. * cubicA2 * cubicA2 * cubicA2 / 27. - cubicA2 * cubicA1 / 3. + cubicA0;
934 std::array<double, 3> cubicRoots;
936 double resolvent = 0.;
938 resolvent = std::max(resolvent, cubicRoots[
index] - cubicA2 / 3.);
941 const double resolventScale = std::max({std::abs(cubicA2), std::sqrt(std::abs(cubicA1)),
942 std::cbrt(std::abs(cubicA0))});
944 const double sqrtTwoResolvent = std::sqrt(2. * resolvent);
945 const double linearTerm = sqrtTwoResolvent * termQ / (4. * resolvent);
946 addQuadraticRoots(-sqrtTwoResolvent, termP / 2. + resolvent + linearTerm);
947 addQuadraticRoots(sqrtTwoResolvent, termP / 2. + resolvent - linearTerm);
954 addBiquadraticRoots();
958 auto quartic = [&](
double x) {
return (((
x + coeffB) *
x + coeffC) *
x + coeffD) *
x + coeffE; };
959 auto quarticDerivative = [&](
double x) {
return ((4. *
x + 3. * coeffB) *
x + 2. * coeffC) *
x + coeffD; };
960 for (
double& root : roots) {
961 for (
int iteration = 0; iteration < 2; ++iteration) {
962 const double step = quartic(root) / quarticDerivative(root);
963 if (std::isfinite(step) && std::abs(step) <= 2.) {
968 for (
double& root : roots) {
1028 static Curve2D makeArc(
const Vec2& arcCenter,
double arcRadius,
double arcStartAngle,
double arcEndAngle)
1032 curve.
center = arcCenter;
1033 curve.
radius = arcRadius;
1048 std::vector<double> splineWeights, std::vector<double> splineKnots)
1052 curve.
degree = splineDegree;
1053 curve.
poles = std::move(splinePoles);
1054 curve.
weights = std::move(splineWeights);
1055 curve.
knots = std::move(splineKnots);
1072 const size_t lastKnot =
knots.size() - 1;
1096 const int lastPole =
static_cast<int>(
poles.size()) - 1;
1097 if (knotValue >=
knots[lastPole + 1]) {
1104 int high = lastPole + 1;
1105 int mid = (low + high) / 2;
1106 while (knotValue <
knots[mid] || knotValue >=
knots[mid + 1]) {
1107 if (knotValue <
knots[mid]) {
1112 mid = (low + high) / 2;
1119 std::vector<double>& basisDeriv)
const
1122 std::vector<std::vector<double>> ndu(p + 1, std::vector<double>(p + 1, 0.));
1123 std::vector<double>
left(p + 1, 0.);
1124 std::vector<double>
right(p + 1, 0.);
1126 for (
int j = 1;
j <= p; ++
j) {
1130 for (
int r = 0;
r <
j; ++
r) {
1132 const double temp = ndu[
r][
j - 1] / ndu[
j][
r];
1133 ndu[
r][
j] = saved +
right[
r + 1] * temp;
1134 saved =
left[
j -
r] * temp;
1138 basis.assign(p + 1, 0.);
1139 basisDeriv.assign(p + 1, 0.);
1140 for (
int j = 0;
j <= p; ++
j) {
1141 basis[
j] = ndu[
j][p];
1144 for (
int r = 0;
r <= p; ++
r) {
1146 const int pk = p - 1;
1148 d += (1. / ndu[pk + 1][
r - 1]) * ndu[
r - 1][pk];
1151 d += (-1. / ndu[pk + 1][
r]) * ndu[
r][pk];
1153 basisDeriv[
r] = d * p;
1163 std::vector<double> basis;
1164 std::vector<double> basisDeriv;
1166 Vec2 weightedSum{0., 0.};
1167 Vec2 weightedDeriv{0., 0.};
1168 double weightTotal = 0.;
1169 double weightDeriv = 0.;
1170 for (
int j = 0;
j <= p; ++
j) {
1171 const int idx = span - p +
j;
1173 weightedSum.uCoord += basis[
j] *
weight *
poles[idx].uCoord;
1174 weightedSum.vCoord += basis[
j] *
weight *
poles[idx].vCoord;
1175 weightTotal += basis[
j] *
weight;
1176 weightedDeriv.uCoord += basisDeriv[
j] *
weight *
poles[idx].uCoord;
1177 weightedDeriv.vCoord += basisDeriv[
j] *
weight *
poles[idx].vCoord;
1178 weightDeriv += basisDeriv[
j] *
weight;
1180 const double invWeight = (std::abs(weightTotal) >
kTolerance) ? 1. / weightTotal : 0.;
1181 pointOut = {weightedSum.
uCoord * invWeight, weightedSum.vCoord * invWeight};
1182 derivativeOut = {(weightedDeriv.uCoord * weightTotal - weightedSum.uCoord * weightDeriv) * invWeight * invWeight,
1183 (weightedDeriv.vCoord * weightTotal - weightedSum.vCoord * weightDeriv) * invWeight * invWeight};
1202 Vec2 startPointValue;
1204 Vec2 unusedDerivative;
1207 samples.push_back(startPointValue);
1216 const size_t firstInterior =
static_cast<size_t>(
degree) + 1;
1217 const size_t endInterior = std::min(
poles.size(),
knots.size());
1218 if (firstInterior >= endInterior) {
1221 const auto begin =
knots.begin() +
static_cast<std::ptrdiff_t
>(firstInterior);
1222 const auto end =
knots.begin() +
static_cast<std::ptrdiff_t
>(endInterior);
1223 const auto firstAbove = std::upper_bound(begin,
end, lowT);
1224 return firstAbove !=
end && *firstAbove < highT;
1238 const size_t firstInterior =
static_cast<size_t>(
degree) + 1;
1239 const size_t endInterior = std::min(
poles.size(),
knots.size());
1242 if (parameter > from && parameter < to) {
1243 breakpoints.push_back(parameter);
1257 const double knotSpan =
bsplineT1() - knotStart;
1258 const double knotMid = knotStart + 0.5 * (from + to) * knotSpan;
1259 size_t spanIndex =
static_cast<size_t>(
degree);
1260 while (spanIndex + 1 <
poles.size() && spanIndex + 1 <
knots.size() &&
knots[spanIndex + 1] <= knotMid) {
1263 const size_t firstPole = spanIndex -
static_cast<size_t>(
degree);
1264 double lowU = std::numeric_limits<double>::infinity();
1265 double highU = -std::numeric_limits<double>::infinity();
1268 highU = std::max(highU,
poles[
index].uCoord);
1270 return (highU >= lowU) ? (highU - lowU) : 0.;
1276 const double low = std::min(angleFrom, angleTo);
1277 const double high = std::max(angleFrom, angleTo);
1278 double variation = 0.;
1279 double previous = low;
1280 const double firstTurn = std::ceil(low /
kPi) *
kPi;
1281 for (
double turn = firstTurn; turn < high; turn +=
kPi) {
1282 variation += std::abs(
radius * (std::cos(turn) - std::cos(previous)));
1285 return variation + std::abs(
radius * (std::cos(high) - std::cos(previous)));
1291 const double tMid = 0.5 * (
t0 +
t1);
1293 Vec2 unusedDerivative;
1297 const auto deviationSq = [&](
const Vec2& point) {
1301 double flatness = deviationSq(midPoint);
1302 for (
const double fraction : {0.25, 0.75}) {
1305 flatness = std::max(flatness, deviationSq(probePoint));
1342 const int nPoles =
static_cast<int>(
poles.size());
1346 if (
static_cast<int>(
knots.size()) != nPoles +
degree + 1) {
1349 if (!
weights.empty() &&
static_cast<int>(
weights.size()) != nPoles) {
1352 for (
const auto& pole :
poles) {
1421 return {derivative.
uCoord * span, derivative.
vCoord * span};
1432 const double length = std::sqrt(delta.uCoord * delta.uCoord + delta.vCoord * delta.vCoord);
1451 const double direction =
sweep() >= 0. ? 1. : -1.;
1452 return {-direction * std::sin(
angle), direction * std::cos(
angle)};
1458 const double totalSweep =
sweep();
1459 const double magnitude = std::abs(totalSweep);
1471 const double totalSweep =
sweep();
1477 return std::max(0., std::min(1., delta / std::abs(totalSweep)));
1483 auto include = [&](
const Vec2& point) {
1492 for (
const auto& pole :
poles) {
1503 auto include = [&](
const Vec2& point) {
1519 template <
typename Include>
1527 for (
double cardinal : cardinalAngles) {
1541 if (polyline.size() < 2) {
1545 const int segments =
static_cast<int>(polyline.size()) - 1;
1546 double bestDistanceSq = std::numeric_limits<double>::infinity();
1547 Vec2 bestPoint = polyline.front();
1548 double bestParameter = 0.;
1550 const Vec2 segmentStart = polyline[
index];
1551 const Vec2 segmentVector = polyline[
index + 1] - segmentStart;
1552 const double segmentLengthSq =
1554 double projection = 0.;
1559 projection = std::max(0., std::min(1., projection));
1561 const Vec2 candidate{segmentStart.
uCoord + projection * segmentVector.
uCoord,
1562 segmentStart.
vCoord + projection * segmentVector.
vCoord};
1564 if (candidateDistanceSq < bestDistanceSq) {
1565 bestDistanceSq = candidateDistanceSq;
1566 bestPoint = candidate;
1570 parameter = bestParameter;
1583 parameter = std::max(0., std::min(1., projection));
1589 if (deltaU * deltaU + deltaV * deltaV <=
kToleranceSq) {
1593 const double angle = std::atan2(deltaV, deltaU);
1602 return startCandidate;
1605 return endCandidate;
1611 double parameter = 0.;
1625 const int order =
bsplineRational() ? std::max(2 * p + 2, 8) : (p + 1);
1626 std::vector<double>
nodes;
1627 std::vector<double> nodeWeights;
1630 const int lastSpan =
static_cast<int>(
poles.size()) - 1;
1631 for (
int spanIndex = p; spanIndex <= lastSpan; ++spanIndex) {
1632 const double spanLow =
knots[spanIndex];
1633 const double spanHigh =
knots[spanIndex + 1];
1634 const double halfSpan = 0.5 * (spanHigh - spanLow);
1638 const double spanMid = 0.5 * (spanLow + spanHigh);
1639 for (
int nodeIndex = 0; nodeIndex < order; ++nodeIndex) {
1640 const double knotValue = spanMid + halfSpan *
nodes[nodeIndex];
1645 nodeWeights[nodeIndex] * halfSpan;
1663 if (polyline.size() < 2) {
1668 const Vec2 segmentStart = polyline[
index];
1669 const Vec2 segmentEnd = polyline[
index + 1];
1670 const Vec2 segmentVector = segmentEnd - segmentStart;
1671 const double segmentLengthSq =
1673 double projection = 0.;
1678 projection = std::max(0., std::min(1., projection));
1680 const Vec2 candidate{segmentStart.
uCoord + projection * segmentVector.
uCoord,
1681 segmentStart.
vCoord + projection * segmentVector.
vCoord};
1685 const bool firstAbove = segmentStart.
vCoord > point.
vCoord;
1686 const bool secondAbove = segmentEnd.
vCoord > point.
vCoord;
1687 if (firstAbove != secondAbove) {
1688 const double intersectU =
1691 if (point.
uCoord < intersectU) {
1703 auto segmentCrossing = [&](
const Vec2&
first,
const Vec2& second,
double exactIntersectU) {
1704 const bool firstAbove =
first.vCoord > point.
vCoord;
1705 const bool secondAbove = second.vCoord > point.
vCoord;
1706 if (firstAbove == secondAbove) {
1709 return point.
uCoord < exactIntersectU;
1715 if (firstAbove == secondAbove) {
1721 return (point.
uCoord < intersectU) ? 1 : 0;
1727 if (polyline.size() < 2) {
1735 const Vec2 second = polyline[
index + 1];
1736 const bool firstAbove =
first.vCoord > point.
vCoord;
1738 if (firstAbove == secondAbove) {
1741 const double intersectU =
1744 if (point.
uCoord < intersectU) {
1752 const double totalSweep =
sweep();
1756 std::array<double, 8> breakParameters{};
1758 breakParameters[breakCount++] = 0.;
1761 const int firstK =
static_cast<int>(std::floor((lowAngle -
kHalfPi) /
kPi)) - 1;
1762 const int lastK =
static_cast<int>(std::ceil((highAngle -
kHalfPi) /
kPi)) + 1;
1763 for (
int k = firstK; k <= lastK && breakCount < 7; ++k) {
1764 const double extremeAngle =
kHalfPi + k *
kPi;
1765 if (extremeAngle <= lowAngle + kTolerance || extremeAngle >= highAngle -
kTolerance) {
1768 const double extremeParameter = (extremeAngle -
startAngle) / totalSweep;
1770 breakParameters[breakCount++] = extremeParameter;
1773 breakParameters[breakCount++] = 1.;
1774 std::sort(breakParameters.begin(), breakParameters.begin() + breakCount);
1777 ratio = std::max(-1., std::min(1., ratio));
1778 const double cosMagnitude = std::sqrt(std::max(0., 1. - ratio * ratio));
1784 const double midAngle =
startAngle + 0.5 * (breakParameters[
index] + breakParameters[
index + 1]) * totalSweep;
1785 const double cosSign = std::cos(midAngle) >= 0. ? 1. : -1.;
1787 if (segmentCrossing(subStart, subEnd, intersectU)) {
1812 const double knotSum =
knots.front() +
knots.back();
1813 std::vector<double> reversedKnots(
knots.size());
1817 knots = std::move(reversedKnots);
1838 return static_cast<int>(
index);
1851 for (
const auto& curve :
curves) {
1870 if (metric.distanceSq(currentEnd, nextStart) > joinTolerance * joinTolerance) {
1889 if ((area > 0.) != wantPositiveArea) {
1903 for (
const auto& curve :
curves) {
1905 curve.bsplineSamples();
1918 for (
auto& curve :
curves) {
1919 curve.reverseInPlace();
1927 for (
const auto& curve :
curves) {
1939 for (
const auto& curve :
curves) {
1940 area += curve.signedAreaContribution();
1948 for (
const auto& curve :
curves) {
1956 for (
const auto& curve :
curves) {
1969 const double bandSq = band * band;
1973 for (
const auto& curve :
curves) {
1975 if (curve.bsplineBandOrCrossings(point, bandSq, crossings)) {
1978 }
else if (curve.distanceSq(point) <= bandSq) {
1981 crossings += curve.rightwardCrossings(point, curve.loopStart(), curve.loopEnd());
2001 for (
const auto& curve :
curves) {
2003 samples.push_back(curve.startPoint());
2007 std::vector<Vec2> curveSamples;
2008 curve.bsplineSampleInto(curveSamples);
2014 const int arcSteps =
2015 std::max(1,
static_cast<int>(std::lround(segmentsPerArc * std::abs(curve.sweep()) /
kTwoPi)));
2016 for (
int step = 0; step < arcSteps; ++step) {
2017 samples.push_back(curve.pointAt(
static_cast<double>(step) / arcSteps));
2032 const double windowCenter = 0.5 * (uMin + uMax);
2038 const Vec2& point,
bool* boundary =
nullptr,
2041 if (boundary !=
nullptr) {
2045 const auto outerClassification = outerWire.
classify(point, lengthFloor);
2050 if (boundary !=
nullptr) {
2055 for (
const auto& innerWire : innerWires) {
2056 const auto innerClassification = innerWire.classify(point, lengthFloor);
2058 if (boundary !=
nullptr) {
2076template <
typename Ant
iderivative>
2080 static thread_local std::vector<double>
nodes;
2081 static thread_local std::vector<double>
weights;
2086 static thread_local std::vector<double> breakpoints;
2087 breakpoints.clear();
2088 breakpoints.push_back(from);
2090 std::sort(breakpoints.begin() + 1, breakpoints.end(),
2091 [forward = (to >= from)](
double first,
double second) { return forward ? first < second : first > second; });
2092 breakpoints.push_back(to);
2096 const double segmentFrom = breakpoints[
segment];
2097 const double segmentTo = breakpoints[
segment + 1];
2098 if (segmentFrom == segmentTo) {
2101 const double travelU = curve.
uVariation(std::min(segmentFrom, segmentTo), std::max(segmentFrom, segmentTo));
2102 const int pieces = std::max(1,
static_cast<int>(std::ceil(travelU /
kContourMaxSpanU)));
2103 for (
int piece = 0; piece < pieces; ++piece) {
2104 const double low = segmentFrom + (segmentTo - segmentFrom) * piece / pieces;
2105 const double high = segmentFrom + (segmentTo - segmentFrom) * (piece + 1) / pieces;
2106 const double half = 0.5 * (high - low);
2107 const double mid = 0.5 * (high + low);
2109 const double parameter = mid +
half *
nodes[nodeIndex];
2120template <
typename Ant
iderivative>
2122 const Antiderivative& antiderivative)
2124 const auto loopIntegral = [&antiderivative](
const CurveWire& wire) {
2127 const auto& curve = wire.curves[
index];
2130 const Vec2 seamFrom = curve.endPoint();
2131 const Vec2 seamTo = wire.curves[(
index + 1) % wire.curves.size()].startPoint();
2141 double total = loopIntegral(outerWire);
2142 for (
const auto& innerWire : innerWires) {
2143 total += loopIntegral(innerWire);
2149template <
typename Integrand>
2151 const Integrand& integrand,
int samplesPerAxis = 128)
2153 Vec2 lower{std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity()};
2154 Vec2 upper{-std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity()};
2159 const double stepU = (
upper.uCoord -
lower.uCoord) / samplesPerAxis;
2160 const double stepV = (
upper.vCoord -
lower.vCoord) / samplesPerAxis;
2161 const double cellArea = stepU * stepV;
2163 for (
int indexU = 0; indexU < samplesPerAxis; ++indexU) {
2164 const double uCoord =
lower.uCoord + (indexU + 0.5) * stepU;
2165 for (
int indexV = 0; indexV < samplesPerAxis; ++indexV) {
2166 const double vCoord =
lower.vCoord + (indexV + 0.5) * stepV;
2168 sum += integrand(uCoord, vCoord) * cellArea;
2177 const std::vector<std::vector<Curve2D>>& innerTrims,
CurveWire& outerWire,
2184 errorMessage = std::string(
"quadric outer trim wire invalid: ") +
wireStatusMessage(status);
2188 innerWires.reserve(innerTrims.size());
2189 for (
const auto& innerLoop : innerTrims) {
2190 CurveWire innerWire;
2192 if (!innerWire.initialize(innerLoop,
WireRole::Inner, innerStatus, metric, joinTolerance)) {
2193 errorMessage = std::string(
"quadric inner trim wire invalid: ") +
wireStatusMessage(innerStatus);
2196 innerWires.push_back(std::move(innerWire));
2198 lower = {std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity()};
2199 upper = {-std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity()};
2202 errorMessage =
"quadric trim wire has non-finite parametric bounds";
2207 Vec2 tightLower{std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity()};
2208 Vec2 tightUpper{-std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity()};
2211 errorMessage =
"quadric trim wire spans more than a full turn in phi";
2226 for (
const auto& curve : wire.
curves) {
2229 std::vector<Vec2> curveSamples;
2230 curve.bsplineSampleInto(curveSamples);
2236 Vec2 lower{std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity()};
2237 Vec2 upper{-std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity()};
2240 int steps = std::max(1,
static_cast<int>(std::lround(segmentsPerTurn * uSpan /
kTwoPi)));
2242 steps = std::max(steps,
static_cast<int>(std::lround(segmentsPerTurn * std::abs(curve.sweep()) /
kTwoPi)));
2243 steps = std::max(steps, 1);
2245 for (
int step = 0; step < steps; ++step) {
2246 samples.push_back(curve.pointAt(
static_cast<double>(step) / steps));
2253template <
typename MapUV>
2255 std::vector<std::array<int, 3>>& triangles)
2259 if (sampledWire.
vertices.size() < 3) {
2262 const int firstVertexIndex =
static_cast<int>(vertices.size());
2263 for (
const auto& sample : sampledWire.
vertices) {
2264 vertices.push_back(mapUV(sample.uCoord, sample.vCoord));
2267 triangles.push_back(
2268 {firstVertexIndex + triangle[0], firstVertexIndex + triangle[1], firstVertexIndex + triangle[2]});
2273template <
typename MapUV>
2275 const MapUV& mapUV,
double orientationSign,
2276 std::vector<std::pair<Vec3, Vec3>>&
edges)
2278 auto appendLoop = [&](
const CurveWire& wire) {
2280 const size_t sampleCount =
samples.size();
2281 for (
size_t sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) {
2283 const Vec2& next =
samples[(sampleIndex + 1) % sampleCount];
2286 if (orientationSign >= 0.) {
2287 edges.emplace_back(edgeStart, edgeEnd);
2289 edges.emplace_back(edgeEnd, edgeStart);
2293 appendLoop(outerWire);
2294 for (
const auto& innerWire : innerWires) {
2295 appendLoop(innerWire);
2303template <
typename MapUV>
2305 size_t index,
const MapUV& mapUV, std::vector<Vec3>&
samples)
2308 size_t local =
index;
2309 if (local < outerWire.
curves.size()) {
2312 local -= outerWire.
curves.size();
2313 for (
const auto& innerWire : innerWires) {
2314 if (local < innerWire.curves.size()) {
2318 local -= innerWire.
curves.size();
2321 if (wire ==
nullptr) {
2328 const Curve2D& curve = wire->
curves[
static_cast<size_t>(stored)];
2339template <
typename MapUV>
2341 size_t index,
const MapUV& mapUV, std::vector<Vec3>&
samples)
2344 size_t local =
index;
2345 if (local < outerWire.
vertices.size()) {
2348 local -= outerWire.
vertices.size();
2349 for (
const auto& innerWire : innerWires) {
2350 if (local < innerWire.vertices.size()) {
2354 local -= innerWire.
vertices.size();
2357 if (wire ==
nullptr) {
2380inline void assembleRims(
const std::vector<std::pair<Vec3, Vec3>>&
edges, std::vector<SurfaceRim>& rims)
2382 if (
edges.empty()) {
2386 using VertexKey = std::tuple<int64_t, int64_t, int64_t>;
2387 auto keyOf = [&](
const Vec3& point) {
2388 return VertexKey{quantize(point.xCoord), quantize(point.yCoord), quantize(point.zCoord)};
2392 std::vector<bool> consumed(
edges.size(),
false);
2393 std::map<VertexKey, std::vector<size_t>> edgesByMidpoint;
2394 for (
size_t edgeIndex = 0; edgeIndex <
edges.size(); ++edgeIndex) {
2395 const Vec3 midpoint = (
edges[edgeIndex].first +
edges[edgeIndex].second) * 0.5;
2396 const auto [xKey, yKey, zKey] = keyOf(midpoint);
2397 bool cancelled =
false;
2398 for (
int64_t dx = -1; dx <= 1 && !cancelled; ++dx) {
2399 for (
int64_t dy = -1; dy <= 1 && !cancelled; ++dy) {
2400 for (
int64_t dz = -1; dz <= 1 && !cancelled; ++dz) {
2401 const auto found = edgesByMidpoint.find(VertexKey{xKey + dx, yKey + dy, zKey + dz});
2402 if (found == edgesByMidpoint.end()) {
2405 for (
const size_t candidate : found->second) {
2406 if (consumed[candidate] ||
2411 consumed[candidate] =
true;
2412 consumed[edgeIndex] =
true;
2420 edgesByMidpoint[keyOf(midpoint)].push_back(edgeIndex);
2424 std::map<VertexKey, std::vector<size_t>> edgesByStart;
2425 for (
size_t edgeIndex = 0; edgeIndex <
edges.size(); ++edgeIndex) {
2426 if (!consumed[edgeIndex]) {
2427 edgesByStart[keyOf(
edges[edgeIndex].
first)].push_back(edgeIndex);
2433 auto findSuccessor = [&](
const Vec3& point) ->
long long {
2434 const auto [xKey, yKey, zKey] = keyOf(point);
2435 for (
int64_t dx = -1; dx <= 1; ++dx) {
2436 for (
int64_t dy = -1; dy <= 1; ++dy) {
2437 for (
int64_t dz = -1; dz <= 1; ++dz) {
2438 const auto found = edgesByStart.find(VertexKey{xKey + dx, yKey + dy, zKey + dz});
2439 if (found == edgesByStart.end()) {
2442 for (
const size_t candidate : found->second) {
2444 return static_cast<long long>(candidate);
2453 for (
size_t seed = 0; seed <
edges.size(); ++seed) {
2454 if (consumed[seed]) {
2457 consumed[seed] =
true;
2467 const long long next = findSuccessor(rim.
points.back());
2471 consumed[
static_cast<size_t>(next)] =
true;
2472 rim.
points.push_back(
edges[
static_cast<size_t>(next)].second);
2474 if (rim.
points.size() >= 2) {
2475 rims.push_back(std::move(rim));
2519 constexpr double kBig = std::numeric_limits<double>::max();
2522 boxes.push_back(
box);
2530 double maxDistance, std::vector<RayHit>& hits)
const = 0;
2559 std::vector<std::array<int, 3>>& triangles)
const = 0;
2567 std::vector<std::pair<Vec3, Vec3>>
edges;
2582 const std::vector<Vec2>& outerWireVertices,
2583 const std::vector<std::vector<Vec2>>& innerWireVertices, std::string& errorMessage)
2586 errorMessage =
"surface frame contains a non-finite value";
2590 mOrigin = surfaceOrigin;
2591 mAxisU = surfaceAxisU;
2592 mAxisV = surfaceAxisV;
2593 const Vec3 normalVector =
cross(mAxisU, mAxisV);
2594 mAreaScale =
norm(normalVector);
2596 errorMessage =
"surface frame axes are degenerate";
2599 mNormal = normalVector * (1. / mAreaScale);
2601 mMetricUU =
dot(mAxisU, mAxisU);
2602 mMetricUV =
dot(mAxisU, mAxisV);
2603 mMetricVV =
dot(mAxisV, mAxisV);
2604 const double metricDet = mMetricUU * mMetricVV - mMetricUV * mMetricUV;
2606 errorMessage =
"surface frame metric is singular";
2609 mInverseMetricDet = 1. / metricDet;
2615 errorMessage = std::string(
"outer wire invalid: ") +
wireStatusMessage(outerStatus);
2620 mInnerWires.clear();
2621 mInnerWires.reserve(innerWireVertices.size());
2622 mInnerReoriented =
false;
2623 for (
const auto& innerWireInput : innerWireVertices) {
2627 errorMessage = std::string(
"inner wire invalid: ") +
wireStatusMessage(innerStatus);
2631 mInnerWires.emplace_back(std::move(innerWire));
2634 const auto ringOf = [
this](
const SurfaceWire& wire) {
2635 std::vector<Vec3> ring;
2636 ring.reserve(wire.vertices.size());
2637 for (
const auto&
vertex : wire.vertices) {
2642 mOuterRing = ringOf(mOuterWire);
2643 mInnerRings.clear();
2644 for (
const auto& innerWire : mInnerWires) {
2645 mInnerRings.push_back(ringOf(innerWire));
2655 return mOrigin + mAxisU * point.
uCoord + mAxisV * point.
vCoord;
2660 const Vec3 relativePoint = point - mOrigin;
2661 const double projectionU =
dot(relativePoint, mAxisU);
2662 const double projectionV =
dot(relativePoint, mAxisV);
2663 return {(projectionU * mMetricVV - projectionV * mMetricUV) * mInverseMetricDet,
2664 (projectionV * mMetricUU - projectionU * mMetricUV) * mInverseMetricDet};
2671 if (boundary !=
nullptr) {
2675 const auto outerClassification = mOuterWire.
classify(point, mTrimBand);
2680 if (boundary !=
nullptr) {
2686 for (
const auto& innerWire : mInnerWires) {
2687 const auto innerClassification = innerWire.classify(point, mTrimBand);
2689 if (boundary !=
nullptr) {
2710 double maxDistance, std::vector<RayHit>& hits)
const override
2712 const double denominator =
dot(mNormal, rayDirection);
2716 const double candidateDistance =
dot(mOrigin - rayOrigin, mNormal) / denominator;
2717 if (candidateDistance < minDistance || candidateDistance > maxDistance) {
2720 const Vec3 candidatePoint = rayOrigin + rayDirection * candidateDistance;
2721 bool onTrimBoundary =
false;
2725 hits.push_back({candidateDistance, mNormal, onTrimBoundary});
2730 double bestDistanceSq = std::numeric_limits<double>::infinity();
2731 for (
size_t vertexIndex = 0; vertexIndex < ring.size(); ++vertexIndex) {
2733 std::min(bestDistanceSq,
pointSegmentDistanceSq(point, ring[vertexIndex], ring[(vertexIndex + 1) % ring.size()]));
2735 return bestDistanceSq;
2743 return signedPlaneDistance * signedPlaneDistance;
2747 for (
const auto& innerRing : mInnerRings) {
2750 return bestDistanceSq;
2757 auto extendPoint = [&](
const Vec2& surfacePoint) {
2770 for (
const auto& innerWire : mInnerWires) {
2771 for (
const auto&
vertex : innerWire.vertices) {
2779 double parametricArea = std::abs(mOuterWire.
signedArea());
2780 for (
const auto& innerWire : mInnerWires) {
2781 parametricArea -= std::abs(innerWire.signedArea());
2783 return std::max(0., parametricArea) * mAreaScale;
2796 void appendDisplayMesh(std::vector<Vec3>& vertices, std::vector<std::array<int, 3>>& triangles)
const override
2798 const int firstVertexIndex =
static_cast<int>(vertices.size());
2804 for (
const auto& triangle : localTriangles) {
2805 triangles.push_back(
2806 {firstVertexIndex + triangle[0], firstVertexIndex + triangle[1], firstVertexIndex + triangle[2]});
2813 for (
size_t vertexIndex = 0; vertexIndex < wire.vertices.size(); ++vertexIndex) {
2814 const Vec3 edgeStart =
toGlobal(wire.vertices[vertexIndex]);
2815 const Vec3 edgeEnd =
toGlobal(wire.vertices[(vertexIndex + 1) % wire.vertices.size()]);
2816 edges.emplace_back(edgeStart, edgeEnd);
2819 appendWire(mOuterWire);
2820 for (
const auto& innerWire : mInnerWires) {
2821 appendWire(innerWire);
2828 mOuterWire, mInnerWires,
index,
2837 double mMetricUU = 0.;
2838 double mMetricUV = 0.;
2839 double mMetricVV = 0.;
2840 double mInverseMetricDet = 0.;
2841 double mAreaScale = 0.;
2842 bool mOuterReoriented =
false;
2843 bool mInnerReoriented =
false;
2844 double mTrimBand = 0.;
2846 std::vector<SurfaceWire> mInnerWires;
2847 std::vector<Vec3> mOuterRing;
2848 std::vector<std::vector<Vec3>> mInnerRings;
2856 const std::vector<Curve2D>& outerCurves,
2857 const std::vector<std::vector<Curve2D>>& innerCurves, std::string& errorMessage,
2861 errorMessage =
"surface frame contains a non-finite value";
2866 errorMessage =
"curved planar surface requires orthonormal frame axes";
2870 mOrigin = surfaceOrigin;
2871 mAxisU = surfaceAxisU;
2872 mAxisV = surfaceAxisV;
2873 mNormal =
cross(mAxisU, mAxisV);
2879 errorMessage = std::string(
"outer wire invalid: ") +
wireStatusMessage(outerStatus);
2884 mInnerWires.clear();
2885 mInnerWires.reserve(innerCurves.size());
2886 for (
const auto& innerCurveLoop : innerCurves) {
2890 errorMessage = std::string(
"inner wire invalid: ") +
wireStatusMessage(innerStatus);
2894 mInnerWires.emplace_back(std::move(innerWire));
2900 for (
const auto& innerWire : mInnerWires) {
2901 mCapacityExact = mCapacityExact && !innerWire.hasBSpline();
2913 const Vec3 relativePoint = point - mOrigin;
2914 return {
dot(relativePoint, mAxisU),
dot(relativePoint, mAxisV)};
2921 if (boundary !=
nullptr) {
2925 const auto outerClassification = mOuterWire.
classify(point, mTrimFloor);
2930 if (boundary !=
nullptr) {
2936 for (
const auto& innerWire : mInnerWires) {
2937 const auto innerClassification = innerWire.classify(point, mTrimFloor);
2939 if (boundary !=
nullptr) {
2960 double maxDistance, std::vector<RayHit>& hits)
const override
2962 const double denominator =
dot(mNormal, rayDirection);
2966 const double candidateDistance =
dot(mOrigin - rayOrigin, mNormal) / denominator;
2967 if (candidateDistance < minDistance || candidateDistance > maxDistance) {
2970 bool onTrimBoundary =
false;
2974 hits.push_back({candidateDistance, mNormal, onTrimBoundary});
2982 return signedPlaneDistance * signedPlaneDistance;
2987 double bestCurveDistanceSq = std::numeric_limits<double>::infinity();
2988 for (
const auto& curve : mOuterWire.
curves) {
2989 bestCurveDistanceSq = std::min(bestCurveDistanceSq, curve.distanceSq(projectedPoint));
2991 for (
const auto& innerWire : mInnerWires) {
2992 for (
const auto& curve : innerWire.curves) {
2993 bestCurveDistanceSq = std::min(bestCurveDistanceSq, curve.distanceSq(projectedPoint));
2996 return bestCurveDistanceSq + signedPlaneDistance * signedPlaneDistance;
3003 Vec2 parametricLower{std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity()};
3004 Vec2 parametricUpper{-std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity()};
3008 for (
const double cornerU : {parametricLower.uCoord, parametricUpper.uCoord}) {
3009 for (
const double cornerV : {parametricLower.vCoord, parametricUpper.vCoord}) {
3010 const Vec3 globalCorner =
toGlobal({cornerU, cornerV});
3023 double parametricArea = std::abs(mOuterWire.
signedArea());
3024 for (
const auto& innerWire : mInnerWires) {
3025 parametricArea -= std::abs(innerWire.signedArea());
3027 return std::max(0., parametricArea);
3043 void appendDisplayMesh(std::vector<Vec3>& vertices, std::vector<std::array<int, 3>>& triangles)
const override
3056 const int firstVertexIndex =
static_cast<int>(vertices.size());
3061 triangles.push_back(
3062 {firstVertexIndex + triangle[0], firstVertexIndex + triangle[1], firstVertexIndex + triangle[2]});
3068 auto appendWire = [&](
const CurveWire& wire) {
3069 const auto samples = wire.sampledBoundary();
3070 for (
size_t sampleIndex = 0; sampleIndex + 1 <
samples.size(); ++sampleIndex) {
3074 appendWire(mOuterWire);
3075 for (
const auto& innerWire : mInnerWires) {
3076 appendWire(innerWire);
3083 mOuterWire, mInnerWires,
index,
3092 bool mReoriented =
false;
3093 bool mCapacityExact =
true;
3094 double mTrimFloor = 0.;
3096 std::vector<CurveWire> mInnerWires;
3101 double phiStart,
double phiSweep,
double heightMin,
double heightMax,
3102 double radiusAtMin,
double radiusAtMax,
3103 std::vector<BoundedSurface::CoverBox>& boxes)
3106 for (
int chunk = 0; chunk < chunks; ++chunk) {
3107 const double phiLow = phiStart + phiSweep * chunk / chunks;
3108 const double phiHigh = phiStart + phiSweep * (chunk + 1) / chunks;
3111 for (
int dimension = 0; dimension < 3; ++dimension) {
3112 double radialLow = 0.;
3113 double radialHigh = 0.;
3117 lower[dimension] = std::min(centerAtMin + radiusAtMin * radialLow, centerAtMax + radiusAtMax * radialLow);
3118 upper[dimension] = std::max(centerAtMin + radiusAtMin * radialHigh, centerAtMax + radiusAtMax * radialHigh);
3129 double heightMin,
double heightMax,
double phiStart,
double phiSweep,
bool innerWall,
3130 std::string& errorMessage)
3132 if (!
finite(centerPoint) || !
finite(axis) || !
finite(referenceAxisU) || !std::isfinite(radius) ||
3133 !std::isfinite(heightMin) || !std::isfinite(heightMax) || !std::isfinite(phiStart) ||
3134 !std::isfinite(phiSweep)) {
3135 errorMessage =
"cylindrical surface parameter is non-finite";
3139 errorMessage =
"cylindrical surface needs a positive radius";
3143 errorMessage =
"cylindrical surface needs a positive height range";
3147 errorMessage =
"cylindrical surface needs an angular sweep in (0, 2pi]";
3150 if (!
makeFrame(axis, referenceAxisU, mAxisU, mAxisV, mAxisW, errorMessage)) {
3154 mCenter = centerPoint;
3157 mHeightMin = heightMin;
3158 mHeightMax = heightMax;
3159 mPhiStart = phiStart;
3160 mPhiSweep = std::min(phiSweep,
kTwoPi);
3161 mNormalSign = innerWall ? -1. : 1.;
3167 double heightMin,
double heightMax,
double phiStart,
double phiSweep,
bool innerWall,
3168 const std::vector<Curve2D>& outerTrim,
const std::vector<std::vector<Curve2D>>& innerTrims,
3171 if (!
initialize(centerPoint, axis, referenceAxisU, radius, heightMin, heightMax, phiStart, phiSweep, innerWall,
3180 mPhiStart =
lower.uCoord;
3182 mHeightMin =
lower.vCoord;
3183 mHeightMax =
upper.vCoord;
3184 mHasWireTrim =
true;
3193 const double uCoord =
unwrapAngleInto(phi, mPhiStart, mPhiStart + mPhiSweep);
3200 std::string& errorMessage)
3203 errorMessage =
"surface axis is degenerate";
3207 const Vec3 projectedU = referenceAxisU - axisW *
dot(referenceAxisU, axisW);
3209 errorMessage =
"surface reference axis is parallel to the main axis";
3213 axisV =
cross(axisW, axisU);
3221 const Vec3 relativePoint = point - mCenter;
3222 return {
dot(relativePoint, mAxisU),
dot(relativePoint, mAxisV),
dot(relativePoint, mAxisW)};
3237 return mCenter + mAxisW *
height + (mAxisU * std::cos(phi) + mAxisV * std::sin(phi)) * mRadius;
3243 const double radialDistance = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
3244 if (std::abs(radialDistance - mRadius) >
kTolerance) {
3250 const double phi = std::atan2(localPoint.
yCoord, localPoint.
xCoord);
3258 double maxDistance, std::vector<RayHit>& hits)
const override
3261 const Vec3 localDirection{
dot(rayDirection, mAxisU),
dot(rayDirection, mAxisV),
dot(rayDirection, mAxisW)};
3263 const double quadraticA = localDirection.
xCoord * localDirection.xCoord +
3264 localDirection.yCoord * localDirection.yCoord;
3268 const double quadraticB = 2. * (localOrigin.
xCoord * localDirection.xCoord +
3269 localOrigin.
yCoord * localDirection.yCoord);
3270 const double quadraticC = localOrigin.
xCoord * localOrigin.
xCoord +
3271 localOrigin.
yCoord * localOrigin.
yCoord - mRadius * mRadius;
3272 const double discriminant = quadraticB * quadraticB - 4. * quadraticA * quadraticC;
3273 if (discriminant <= 0.) {
3276 const double sqrtDiscriminant = std::sqrt(discriminant);
3277 const double firstRoot = (-quadraticB - sqrtDiscriminant) / (2. * quadraticA);
3278 const double secondRoot = (-quadraticB + sqrtDiscriminant) / (2. * quadraticA);
3283 for (
const double candidate : {firstRoot, secondRoot}) {
3284 if (candidate < minDistance || candidate > maxDistance) {
3287 const double hitU = localOrigin.
xCoord + candidate * localDirection.xCoord;
3288 const double hitV = localOrigin.
yCoord + candidate * localDirection.yCoord;
3289 const double hitHeight = localOrigin.
zCoord + candidate * localDirection.zCoord;
3290 const double hitPhi = std::atan2(hitV, hitU);
3291 bool onTrimBoundary =
false;
3293 if (!
pointInTrim(hitPhi, hitHeight, &onTrimBoundary)) {
3299 const double radialDistance = std::hypot(hitU, hitV);
3300 const Vec3 hitNormal = (mAxisU * (hitU / radialDistance) + mAxisV * (hitV / radialDistance)) * mNormalSign;
3301 hits.push_back({candidate, hitNormal, onTrimBoundary});
3309 const double radialDistance = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
3312 Vec2{mRadius, mHeightMax});
3314 const double distanceToStartSeam =
3316 const double endPhi = mPhiStart + mPhiSweep;
3317 const double distanceToEndSeam =
3319 return std::min(distanceToStartSeam, distanceToEndSeam);
3325 const double radialDistance = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
3327 return mAxisU * mNormalSign;
3329 return (mAxisU * (localPoint.
xCoord / radialDistance) + mAxisV * (localPoint.
yCoord / radialDistance)) *
3343 const double centreU =
dot(mCenter, mAxisU);
3344 const double centreV =
dot(mCenter, mAxisV);
3345 const double factor = mNormalSign * mRadius / 3.;
3347 return factor * (centreU * std::sin(phi) - centreV * std::cos(phi) + mRadius * phi);
3350 const double endPhi = mPhiStart + mPhiSweep;
3351 const double phiFactor =
dot(mCenter, mAxisU) * (std::sin(endPhi) - std::sin(mPhiStart)) -
3352 dot(mCenter, mAxisV) * (std::cos(endPhi) - std::cos(mPhiStart));
3353 const double height = mHeightMax - mHeightMin;
3354 return mNormalSign * mRadius *
height * (phiFactor + mRadius * mPhiSweep) / 3.;
3362 for (
const double height : {mHeightMin, mHeightMax}) {
3363 const Vec3 rimCenter = mCenter + mAxisW *
height;
3364 for (
int dimension = 0; dimension < 3; ++dimension) {
3365 const double radialExtent = mRadius * std::hypot(
component(mAxisU, dimension),
component(mAxisV, dimension));
3366 const double centerValue =
component(rimCenter, dimension);
3367 if (dimension == 0) {
3368 lower.xCoord = std::min(
lower.xCoord, centerValue - radialExtent);
3369 upper.xCoord = std::max(
upper.xCoord, centerValue + radialExtent);
3370 }
else if (dimension == 1) {
3371 lower.yCoord = std::min(
lower.yCoord, centerValue - radialExtent);
3372 upper.yCoord = std::max(
upper.yCoord, centerValue + radialExtent);
3374 lower.zCoord = std::min(
lower.zCoord, centerValue - radialExtent);
3375 upper.zCoord = std::max(
upper.zCoord, centerValue + radialExtent);
3384 appendArcBandCoverBoxes(mCenter, mAxisU, mAxisV, mAxisW, mPhiStart, mPhiSweep, mHeightMin, mHeightMax, mRadius,
3392 return std::max(1,
static_cast<int>(std::lround(
kArcSamples * mPhiSweep /
kTwoPi)));
3395 void appendDisplayMesh(std::vector<Vec3>& vertices, std::vector<std::array<int, 3>>& triangles)
const override
3402 const int firstVertexIndex =
static_cast<int>(vertices.size());
3403 for (
int step = 0; step <=
segments; ++step) {
3404 const double phi = mPhiStart + mPhiSweep * step /
segments;
3405 vertices.push_back(
pointAt(phi, mHeightMin));
3406 vertices.push_back(
pointAt(phi, mHeightMax));
3408 for (
int step = 0; step <
segments; ++step) {
3409 const int base = firstVertexIndex + 2 * step;
3410 triangles.push_back({base, base + 2, base + 3});
3411 triangles.push_back({base, base + 3, base + 1});
3425 auto emitEdge = [&](
const Vec3& edgeStart,
const Vec3& edgeEnd) {
3426 if (mNormalSign > 0.) {
3427 edges.emplace_back(edgeStart, edgeEnd);
3429 edges.emplace_back(edgeEnd, edgeStart);
3432 for (
int step = 0; step <
segments; ++step) {
3433 const double phi = mPhiStart + mPhiSweep * step /
segments;
3434 const double nextPhi = mPhiStart + mPhiSweep * (step + 1) /
segments;
3435 emitEdge(
pointAt(phi, mHeightMin),
pointAt(nextPhi, mHeightMin));
3436 emitEdge(
pointAt(nextPhi, mHeightMax),
pointAt(phi, mHeightMax));
3439 const double endPhi = mPhiStart + mPhiSweep;
3440 emitEdge(
pointAt(endPhi, mHeightMin),
pointAt(endPhi, mHeightMax));
3441 emitEdge(
pointAt(mPhiStart, mHeightMax),
pointAt(mPhiStart, mHeightMin));
3447 if (!mHasWireTrim) {
3458 double mRadius = 0.;
3459 double mHeightMin = 0.;
3460 double mHeightMax = 0.;
3461 double mPhiStart = 0.;
3462 double mPhiSweep =
kTwoPi;
3463 double mPhiTolerance = 0.;
3464 double mNormalSign = 1.;
3465 bool mHasWireTrim =
false;
3467 std::vector<CurveWire> mTrimInner;
3475 double thetaMin,
double thetaMax,
double phiStart,
double phiSweep,
bool innerWall,
3476 std::string& errorMessage)
3479 !std::isfinite(thetaMin) || !std::isfinite(thetaMax) || !std::isfinite(phiStart) ||
3480 !std::isfinite(phiSweep)) {
3481 errorMessage =
"spherical surface parameter is non-finite";
3485 errorMessage =
"spherical surface needs a positive radius";
3489 errorMessage =
"spherical surface needs a polar range within [0, pi]";
3493 errorMessage =
"spherical surface needs an angular sweep in (0, 2pi]";
3502 mThetaMin = std::max(0., thetaMin);
3503 mThetaMax = std::min(
kPi, thetaMax);
3504 mPhiStart = phiStart;
3505 mPhiSweep = std::min(phiSweep,
kTwoPi);
3506 mNormalSign = innerWall ? -1. : 1.;
3512 double thetaMin,
double thetaMax,
double phiStart,
double phiSweep,
bool innerWall,
3513 const std::vector<Curve2D>& outerTrim,
const std::vector<std::vector<Curve2D>>& innerTrims,
3516 if (!
initialize(
center, polarAxis, referenceAxisU, radius, thetaMin, thetaMax, phiStart, phiSweep, innerWall,
3525 mPhiStart =
lower.uCoord;
3527 mThetaMin = std::max(0.,
lower.vCoord);
3528 mThetaMax = std::min(
kPi,
upper.vCoord);
3529 mHasWireTrim =
true;
3536 bool pointInTrim(
double phi,
double theta,
bool* boundary =
nullptr)
const
3538 const double uCoord =
unwrapAngleInto(phi, mPhiStart, mPhiStart + mPhiSweep);
3546 const Vec3 relativePoint = point - mCenter;
3547 return {
dot(relativePoint, mAxisU),
dot(relativePoint, mAxisV),
dot(relativePoint, mAxisW)};
3552 if (boundary !=
nullptr) {
3555 const double pointRadius =
norm(localPoint);
3560 const double theta = std::acos(std::max(-1., std::min(1., localPoint.
zCoord / pointRadius)));
3561 const double transverseDistance = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
3565 return theta >= mThetaMin - thetaTolerance && theta <= mThetaMax + thetaTolerance;
3569 if (theta < mThetaMin - thetaTolerance || theta > mThetaMax + thetaTolerance) {
3581 const double sinTheta = std::sin(theta);
3582 return mCenter + (mAxisU * (sinTheta * std::cos(phi)) + mAxisV * (sinTheta * std::sin(phi)) +
3583 mAxisW * std::cos(theta)) *
3597 double maxDistance, std::vector<RayHit>& hits)
const override
3599 const Vec3 relativeOrigin = rayOrigin - mCenter;
3600 const double quadraticA =
normSq(rayDirection);
3604 const double quadraticB = 2. *
dot(relativeOrigin, rayDirection);
3605 const double quadraticC =
normSq(relativeOrigin) - mRadius * mRadius;
3606 const double discriminant = quadraticB * quadraticB - 4. * quadraticA * quadraticC;
3607 if (discriminant <= 0.) {
3610 const double sqrtDiscriminant = std::sqrt(discriminant);
3611 const double firstRoot = (-quadraticB - sqrtDiscriminant) / (2. * quadraticA);
3612 const double secondRoot = (-quadraticB + sqrtDiscriminant) / (2. * quadraticA);
3617 for (
const double candidate : {firstRoot, secondRoot}) {
3618 if (candidate < minDistance || candidate > maxDistance) {
3621 const Vec3 localHit =
toLocal(rayOrigin + rayDirection * candidate);
3622 bool onTrimBoundary =
false;
3626 hits.push_back({candidate,
3628 (mNormalSign / mRadius),
3637 const double radialOffset =
norm(localPoint) - mRadius;
3638 return radialOffset * radialOffset;
3644 const double pointRadius =
norm(localPoint);
3646 return mAxisW * mNormalSign;
3648 return (mAxisU * localPoint.
xCoord + mAxisV * localPoint.
yCoord + mAxisW * localPoint.
zCoord) *
3649 (mNormalSign / pointRadius);
3662 const double centreU =
dot(mCenter, mAxisU);
3663 const double centreV =
dot(mCenter, mAxisV);
3664 const double centreW =
dot(mCenter, mAxisW);
3665 const double factor = mNormalSign * mRadius * mRadius / 3.;
3667 const double sinTheta = std::sin(theta);
3668 return factor * sinTheta *
3669 (sinTheta * (centreU * std::sin(phi) - centreV * std::cos(phi)) +
3670 (centreW * std::cos(theta) + mRadius) * phi);
3673 const double endPhi = mPhiStart + mPhiSweep;
3674 const double phiFactor =
dot(mCenter, mAxisU) * (std::sin(endPhi) - std::sin(mPhiStart)) -
3675 dot(mCenter, mAxisV) * (std::cos(endPhi) - std::cos(mPhiStart));
3676 const double thetaIntegralSinSq =
3677 0.5 * ((mThetaMax - std::sin(mThetaMax) * std::cos(mThetaMax)) -
3678 (mThetaMin - std::sin(mThetaMin) * std::cos(mThetaMin)));
3679 const double thetaIntegralSinCos =
3680 0.5 * (std::sin(mThetaMax) * std::sin(mThetaMax) - std::sin(mThetaMin) * std::sin(mThetaMin));
3681 const double thetaIntegralSin = std::cos(mThetaMin) - std::cos(mThetaMax);
3682 return mNormalSign * mRadius * mRadius *
3683 (phiFactor * thetaIntegralSinSq +
dot(mCenter, mAxisW) * mPhiSweep * thetaIntegralSinCos +
3684 mRadius * mPhiSweep * thetaIntegralSin) /
3705 for (
int thetaChunk = 0; thetaChunk < thetaChunks; ++thetaChunk) {
3706 const double thetaLow =
kPi * thetaChunk / thetaChunks;
3707 const double thetaHigh =
kPi * (thetaChunk + 1) / thetaChunks;
3708 for (
int phiChunk = 0; phiChunk < phiChunks; ++phiChunk) {
3709 const double phiLow =
kTwoPi * phiChunk / phiChunks;
3710 const double phiHigh =
kTwoPi * (phiChunk + 1) / phiChunks;
3713 for (
int dimension = 0; dimension < 3; ++dimension) {
3714 double inPlaneLow = 0.;
3715 double inPlaneHigh = 0.;
3719 const double axisComponent =
component(mAxisW, dimension);
3720 const double high =
sinusoidMaximum(axisComponent, inPlaneHigh, thetaLow, thetaHigh);
3721 const double low =
sinusoidMinimum(axisComponent, inPlaneLow, thetaLow, thetaHigh);
3722 lower[dimension] =
component(mCenter, dimension) + mRadius * low;
3723 upper[dimension] =
component(mCenter, dimension) + mRadius * high;
3732 return std::max(1,
static_cast<int>(std::lround(
kArcSamples * mPhiSweep /
kTwoPi)));
3737 return std::max(1,
static_cast<int>(std::lround(
kArcSamples * (mThetaMax - mThetaMin) /
kTwoPi)));
3740 void appendDisplayMesh(std::vector<Vec3>& vertices, std::vector<std::array<int, 3>>& triangles)
const override
3748 const int firstVertexIndex =
static_cast<int>(vertices.size());
3749 for (
int thetaStep = 0; thetaStep <= thetaSteps; ++thetaStep) {
3750 const double theta = mThetaMin + (mThetaMax - mThetaMin) * thetaStep / thetaSteps;
3751 for (
int phiStep = 0; phiStep <= phiSteps; ++phiStep) {
3752 vertices.push_back(
pointAt(theta, mPhiStart + mPhiSweep * phiStep / phiSteps));
3755 const int rowLength = phiSteps + 1;
3756 for (
int thetaStep = 0; thetaStep < thetaSteps; ++thetaStep) {
3757 for (
int phiStep = 0; phiStep < phiSteps; ++phiStep) {
3758 const int base = firstVertexIndex + thetaStep * rowLength + phiStep;
3759 triangles.push_back({base, base + 1, base + rowLength + 1});
3760 triangles.push_back({base, base + rowLength + 1, base + rowLength});
3775 auto emitEdge = [&](
const Vec3& edgeStart,
const Vec3& edgeEnd) {
3776 if (mNormalSign > 0.) {
3777 edges.emplace_back(edgeStart, edgeEnd);
3779 edges.emplace_back(edgeEnd, edgeStart);
3784 const double endPhi = mPhiStart + mPhiSweep;
3785 if (mThetaMin > thetaTolerance) {
3786 for (
int step = 0; step < phiSteps; ++step) {
3787 const double phi = mPhiStart + mPhiSweep * step / phiSteps;
3788 const double nextPhi = mPhiStart + mPhiSweep * (step + 1) / phiSteps;
3792 if (mThetaMax <
kPi - thetaTolerance) {
3793 for (
int step = 0; step < phiSteps; ++step) {
3794 const double phi = mPhiStart + mPhiSweep * step / phiSteps;
3795 const double nextPhi = mPhiStart + mPhiSweep * (step + 1) / phiSteps;
3801 for (
int step = 0; step < thetaSteps; ++step) {
3802 const double theta = mThetaMin + (mThetaMax - mThetaMin) * step / thetaSteps;
3803 const double nextTheta = mThetaMin + (mThetaMax - mThetaMin) * (step + 1) / thetaSteps;
3804 emitEdge(
pointAt(theta, mPhiStart),
pointAt(nextTheta, mPhiStart));
3812 if (!mHasWireTrim) {
3823 double mRadius = 0.;
3824 double mThetaMin = 0.;
3825 double mThetaMax =
kPi;
3826 double mPhiStart = 0.;
3827 double mPhiSweep =
kTwoPi;
3828 double mNormalSign = 1.;
3829 bool mHasWireTrim =
false;
3831 std::vector<CurveWire> mTrimInner;
3839 double radiusAtMax,
double heightMin,
double heightMax,
double phiStart,
double phiSweep,
3840 bool innerWall, std::string& errorMessage)
3842 if (!
finite(centerPoint) || !
finite(axis) || !
finite(referenceAxisU) || !std::isfinite(radiusAtMin) ||
3843 !std::isfinite(radiusAtMax) || !std::isfinite(heightMin) || !std::isfinite(heightMax) ||
3844 !std::isfinite(phiStart) || !std::isfinite(phiSweep)) {
3845 errorMessage =
"conical surface parameter is non-finite";
3849 std::max(radiusAtMin, radiusAtMax) <=
kTolerance) {
3850 errorMessage =
"conical surface needs non-negative radii, at least one positive";
3854 errorMessage =
"conical surface needs a positive height range";
3858 errorMessage =
"conical surface needs an angular sweep in (0, 2pi]";
3865 mCenter = centerPoint;
3866 mHeightMin = heightMin;
3867 mHeightMax = heightMax;
3868 mSlope = (radiusAtMax - radiusAtMin) / (heightMax - heightMin);
3869 mRadius0 = radiusAtMin - mSlope * heightMin;
3870 mPhiStart = phiStart;
3871 mPhiSweep = std::min(phiSweep,
kTwoPi);
3872 mNormalSign = innerWall ? -1. : 1.;
3879 double radiusAtMax,
double heightMin,
double heightMax,
double phiStart,
double phiSweep,
3880 bool innerWall,
const std::vector<Curve2D>& outerTrim,
3881 const std::vector<std::vector<Curve2D>>& innerTrims, std::string& errorMessage,
3884 if (!
initialize(centerPoint, axis, referenceAxisU, radiusAtMin, radiusAtMax, heightMin, heightMax, phiStart,
3885 phiSweep, innerWall, errorMessage)) {
3893 mPhiStart =
lower.uCoord;
3895 mHeightMin =
lower.vCoord;
3896 mHeightMax =
upper.vCoord;
3898 mHasWireTrim =
true;
3907 const double uCoord =
unwrapAngleInto(phi, mPhiStart, mPhiStart + mPhiSweep);
3919 const Vec3 relativePoint = point - mCenter;
3920 return {
dot(relativePoint, mAxisU),
dot(relativePoint, mAxisV),
dot(relativePoint, mAxisW)};
3935 return mCenter + mAxisW *
height + (mAxisU * std::cos(phi) + mAxisV * std::sin(phi)) *
radiusAt(
height);
3942 const double radialDistance = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
3944 if (std::abs(radialDistance - surfaceRadius) >
kTolerance * std::sqrt(1. + mSlope * mSlope)) {
3950 const double phi = std::atan2(localPoint.
yCoord, localPoint.
xCoord);
3958 double maxDistance, std::vector<RayHit>& hits)
const override
3961 const Vec3 localDirection{
dot(rayDirection, mAxisU),
dot(rayDirection, mAxisV),
dot(rayDirection, mAxisW)};
3964 const double surfaceRadiusAtOrigin = mRadius0 + mSlope * localOrigin.
zCoord;
3965 const double quadraticA = localDirection.xCoord * localDirection.xCoord +
3966 localDirection.yCoord * localDirection.yCoord -
3967 mSlope * mSlope * localDirection.zCoord * localDirection.zCoord;
3968 const double quadraticB = 2. * (localOrigin.
xCoord * localDirection.xCoord +
3969 localOrigin.
yCoord * localDirection.yCoord -
3970 mSlope * localDirection.zCoord * surfaceRadiusAtOrigin);
3971 const double quadraticC = localOrigin.
xCoord * localOrigin.
xCoord +
3973 surfaceRadiusAtOrigin * surfaceRadiusAtOrigin;
3975 std::array<double, 2> candidates{};
3976 int candidateCount = 0;
3981 candidates[candidateCount++] = -quadraticC / quadraticB;
3983 const double discriminant = quadraticB * quadraticB - 4. * quadraticA * quadraticC;
3984 if (discriminant <= 0.) {
3987 const double sqrtDiscriminant = std::sqrt(discriminant);
3988 const double firstRoot = (-quadraticB - sqrtDiscriminant) / (2. * quadraticA);
3989 const double secondRoot = (-quadraticB + sqrtDiscriminant) / (2. * quadraticA);
3993 candidates[candidateCount++] = std::min(firstRoot, secondRoot);
3994 candidates[candidateCount++] = std::max(firstRoot, secondRoot);
3997 for (
int candidateIndex = 0; candidateIndex < candidateCount; ++candidateIndex) {
3998 const double candidate = candidates[candidateIndex];
3999 if (candidate < minDistance || candidate > maxDistance) {
4002 const double hitHeight = localOrigin.
zCoord + candidate * localDirection.zCoord;
4003 const double hitSurfaceRadius =
radiusAt(hitHeight);
4007 const double hitU = localOrigin.
xCoord + candidate * localDirection.xCoord;
4008 const double hitV = localOrigin.
yCoord + candidate * localDirection.yCoord;
4009 const double radialDistance = std::hypot(hitU, hitV);
4013 const double hitPhi = std::atan2(hitV, hitU);
4014 bool onTrimBoundary =
false;
4016 if (!
pointInTrim(hitPhi, hitHeight, &onTrimBoundary)) {
4022 const double normalScale = mNormalSign / std::sqrt(1. + mSlope * mSlope);
4023 const Vec3 hitNormal =
4024 (mAxisU * (hitU / radialDistance) + mAxisV * (hitV / radialDistance) - mAxisW * mSlope) * normalScale;
4025 hits.push_back({candidate, hitNormal, onTrimBoundary});
4033 const double radialDistance = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
4038 const double endPhi = mPhiStart + mPhiSweep;
4039 const double distanceToStartSeam =
4041 const double distanceToEndSeam =
4043 return std::min(distanceToStartSeam, distanceToEndSeam);
4049 const double radialDistance = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
4050 const double normalScale = mNormalSign / std::sqrt(1. + mSlope * mSlope);
4052 return (mAxisU - mAxisW * mSlope) * normalScale;
4054 return (mAxisU * (localPoint.
xCoord / radialDistance) + mAxisV * (localPoint.
yCoord / radialDistance) -
4069 const double centreU =
dot(mCenter, mAxisU);
4070 const double centreV =
dot(mCenter, mAxisV);
4071 const double centreW =
dot(mCenter, mAxisW);
4074 return mNormalSign / 3. * localRadius *
4075 (centreU * std::sin(phi) - centreV * std::cos(phi) +
4076 (localRadius - mSlope * (centreW +
height)) * phi);
4079 const double endPhi = mPhiStart + mPhiSweep;
4080 const double phiFactor =
dot(mCenter, mAxisU) * (std::sin(endPhi) - std::sin(mPhiStart)) -
4081 dot(mCenter, mAxisV) * (std::cos(endPhi) - std::cos(mPhiStart));
4082 const double radiusIntegral = mRadius0 * (mHeightMax - mHeightMin) +
4083 0.5 * mSlope * (mHeightMax * mHeightMax - mHeightMin * mHeightMin);
4084 return mNormalSign * radiusIntegral *
4085 (phiFactor + (mRadius0 - mSlope *
dot(mCenter, mAxisW)) * mPhiSweep) / 3.;
4092 for (
const double height : {mHeightMin, mHeightMax}) {
4093 const Vec3 rimCenter = mCenter + mAxisW *
height;
4095 for (
int dimension = 0; dimension < 3; ++dimension) {
4096 const double radialExtent =
4098 const double centerValue =
component(rimCenter, dimension);
4099 if (dimension == 0) {
4100 lower.xCoord = std::min(
lower.xCoord, centerValue - radialExtent);
4101 upper.xCoord = std::max(
upper.xCoord, centerValue + radialExtent);
4102 }
else if (dimension == 1) {
4103 lower.yCoord = std::min(
lower.yCoord, centerValue - radialExtent);
4104 upper.yCoord = std::max(
upper.yCoord, centerValue + radialExtent);
4106 lower.zCoord = std::min(
lower.zCoord, centerValue - radialExtent);
4107 upper.zCoord = std::max(
upper.zCoord, centerValue + radialExtent);
4117 std::max(0.,
radiusAt(mHeightMin)), std::max(0.,
radiusAt(mHeightMax)), boxes);
4122 return std::max(1,
static_cast<int>(std::lround(
kArcSamples * mPhiSweep /
kTwoPi)));
4125 void appendDisplayMesh(std::vector<Vec3>& vertices, std::vector<std::array<int, 3>>& triangles)
const override
4132 const int firstVertexIndex =
static_cast<int>(vertices.size());
4133 for (
int step = 0; step <=
segments; ++step) {
4134 const double phi = mPhiStart + mPhiSweep * step /
segments;
4135 vertices.push_back(
pointAt(phi, mHeightMin));
4136 vertices.push_back(
pointAt(phi, mHeightMax));
4138 for (
int step = 0; step <
segments; ++step) {
4139 const int base = firstVertexIndex + 2 * step;
4142 triangles.push_back({base, base + 2, base + 3});
4145 triangles.push_back({base, base + 3, base + 1});
4161 auto emitEdge = [&](
const Vec3& edgeStart,
const Vec3& edgeEnd) {
4162 if (mNormalSign > 0.) {
4163 edges.emplace_back(edgeStart, edgeEnd);
4165 edges.emplace_back(edgeEnd, edgeStart);
4168 for (
int step = 0; step <
segments; ++step) {
4169 const double phi = mPhiStart + mPhiSweep * step /
segments;
4170 const double nextPhi = mPhiStart + mPhiSweep * (step + 1) /
segments;
4172 emitEdge(
pointAt(phi, mHeightMin),
pointAt(nextPhi, mHeightMin));
4175 emitEdge(
pointAt(nextPhi, mHeightMax),
pointAt(phi, mHeightMax));
4179 const double endPhi = mPhiStart + mPhiSweep;
4180 emitEdge(
pointAt(endPhi, mHeightMin),
pointAt(endPhi, mHeightMax));
4181 emitEdge(
pointAt(mPhiStart, mHeightMax),
pointAt(mPhiStart, mHeightMin));
4187 if (!mHasWireTrim) {
4198 double mRadius0 = 0.;
4200 double mHeightMin = 0.;
4201 double mHeightMax = 0.;
4202 double mPhiStart = 0.;
4203 double mPhiSweep =
kTwoPi;
4204 double mPhiTolerance = 0.;
4205 double mNormalSign = 1.;
4206 bool mHasWireTrim =
false;
4208 std::vector<CurveWire> mTrimInner;
4217 double minorRadius,
double phiStart,
double phiSweep,
double tubeStart,
double tubeSweep,
4218 bool innerWall, std::string& errorMessage)
4220 if (!
finite(centerPoint) || !
finite(axis) || !
finite(referenceAxisU) || !std::isfinite(majorRadius) ||
4221 !std::isfinite(minorRadius) || !std::isfinite(phiStart) || !std::isfinite(phiSweep) ||
4222 !std::isfinite(tubeStart) || !std::isfinite(tubeSweep)) {
4223 errorMessage =
"toroidal surface parameter is non-finite";
4227 errorMessage =
"toroidal surface needs positive major and minor radii";
4231 errorMessage =
"toroidal surface needs a ring sweep in (0, 2pi]";
4235 errorMessage =
"toroidal surface needs a tube sweep in (0, 2pi]";
4242 mCenter = centerPoint;
4243 mMajorRadius = majorRadius;
4244 mMinorRadius = minorRadius;
4247 mPhiStart = phiStart;
4248 mPhiSweep = std::min(phiSweep,
kTwoPi);
4249 mTubeStart = tubeStart;
4250 mTubeSweep = std::min(tubeSweep,
kTwoPi);
4251 mNormalSign = innerWall ? -1. : 1.;
4257 double minorRadius,
double phiStart,
double phiSweep,
double tubeStart,
double tubeSweep,
4258 bool innerWall,
const std::vector<Curve2D>& outerTrim,
4259 const std::vector<std::vector<Curve2D>>& innerTrims, std::string& errorMessage,
4262 if (!
initialize(centerPoint, axis, referenceAxisU, majorRadius, minorRadius, phiStart, phiSweep, tubeStart,
4263 tubeSweep, innerWall, errorMessage)) {
4272 errorMessage =
"toroidal trim wire spans more than a full turn in the tube angle";
4275 mPhiStart =
lower.uCoord;
4277 mTubeStart =
lower.vCoord;
4279 mHasWireTrim =
true;
4286 bool pointInTrim(
double phiRing,
double phiTube,
bool* boundary =
nullptr)
const
4288 const double uCoord =
unwrapAngleInto(phiRing, mPhiStart, mPhiStart + mPhiSweep);
4289 const double vCoord =
unwrapAngleInto(phiTube, mTubeStart, mTubeStart + mTubeSweep);
4298 const Vec3 relativePoint = point - mCenter;
4299 return {
dot(relativePoint, mAxisU),
dot(relativePoint, mAxisV),
dot(relativePoint, mAxisW)};
4314 const double ringRadius = mMajorRadius + mMinorRadius * std::cos(phiTube);
4315 return mCenter + (mAxisU * std::cos(phiRing) + mAxisV * std::sin(phiRing)) * ringRadius +
4316 mAxisW * (mMinorRadius * std::sin(phiTube));
4322 const double rho = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
4324 return mAxisW * (localPoint.
zCoord >= 0. ? mNormalSign : -mNormalSign);
4326 const double radialFactor = (rho - mMajorRadius) / rho;
4330 return mAxisU * mNormalSign;
4338 const double rho = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
4339 const double meridianDistance = std::hypot(rho - mMajorRadius, localPoint.
zCoord) - mMinorRadius;
4340 if (std::abs(meridianDistance) >
kTolerance) {
4343 const double phiTube = std::atan2(localPoint.
zCoord, rho - mMajorRadius);
4347 const double phiRing = std::atan2(localPoint.
yCoord, localPoint.
xCoord);
4355 double maxDistance, std::vector<RayHit>& hits)
const override
4358 const Vec3 localDirection{
dot(rayDirection, mAxisU),
dot(rayDirection, mAxisV),
dot(rayDirection, mAxisW)};
4362 const double dirDotDir =
normSq(localDirection);
4366 const double originDotDir =
dot(localOrigin, localDirection);
4367 const double originDotOrigin =
normSq(localOrigin);
4368 const double constantK = mMajorRadius * mMajorRadius - mMinorRadius * mMinorRadius;
4369 const double transverseE = localDirection.xCoord * localDirection.xCoord +
4370 localDirection.yCoord * localDirection.yCoord;
4371 const double transverseF = localOrigin.
xCoord * localDirection.xCoord +
4372 localOrigin.
yCoord * localDirection.yCoord;
4373 const double transverseG = localOrigin.
xCoord * localOrigin.
xCoord +
4375 const double fourRSquared = 4. * mMajorRadius * mMajorRadius;
4377 const double coeff4 = dirDotDir * dirDotDir;
4378 const double coeff3 = 4. * dirDotDir * originDotDir;
4379 const double coeff2 =
4380 4. * originDotDir * originDotDir + 2. * dirDotDir * (originDotOrigin + constantK) - fourRSquared * transverseE;
4381 const double coeff1 = 4. * originDotDir * (originDotOrigin + constantK) - 2. * fourRSquared * transverseF;
4382 const double coeff0 = (originDotOrigin + constantK) * (originDotOrigin + constantK) - fourRSquared * transverseG;
4385 if (candidates.
empty()) {
4388 std::sort(candidates.
begin(), candidates.
end());
4391 size_t rootIndex = 0;
4392 while (rootIndex < candidates.
size()) {
4393 size_t clusterEnd = rootIndex + 1;
4394 double clusterSum = candidates[rootIndex];
4395 while (clusterEnd < candidates.
size() &&
sameIntersection(candidates[clusterEnd], candidates[clusterEnd - 1])) {
4396 clusterSum += candidates[clusterEnd];
4399 const size_t clusterSize = clusterEnd - rootIndex;
4400 rootIndex = clusterEnd;
4404 const double candidate = clusterSum /
static_cast<double>(
clusterSize);
4405 if (candidate < minDistance || candidate > maxDistance) {
4408 const Vec3 localHit =
toLocal(rayOrigin + rayDirection * candidate);
4409 const double rho = std::hypot(localHit.
xCoord, localHit.
yCoord);
4413 const double phiTube = std::atan2(localHit.
zCoord, rho - mMajorRadius);
4414 const double phiRing = std::atan2(localHit.
yCoord, localHit.
xCoord);
4415 bool onTrimBoundary =
false;
4417 if (!
pointInTrim(phiRing, phiTube, &onTrimBoundary)) {
4423 hits.push_back({candidate,
localNormal(localHit), onTrimBoundary});
4431 const double rho = std::hypot(localPoint.
xCoord, localPoint.
yCoord);
4432 const double meridianDistance = std::hypot(rho - mMajorRadius, localPoint.
zCoord) - mMinorRadius;
4433 return meridianDistance * meridianDistance;
4448 const double centreU =
dot(mCenter, mAxisU);
4449 const double centreV =
dot(mCenter, mAxisV);
4450 const double centreW =
dot(mCenter, mAxisW);
4452 const double cosTube = std::cos(phiTube);
4453 const double sinTube = std::sin(phiTube);
4454 const double rho = mMajorRadius + mMinorRadius * cosTube;
4455 return mNormalSign * mMinorRadius * rho / 3. *
4456 (cosTube * (centreU * std::sin(phiRing) - centreV * std::cos(phiRing)) +
4457 (centreW * sinTube + rho * cosTube + mMinorRadius * sinTube * sinTube) * phiRing);
4461 const double majorR = mMajorRadius;
4462 const double minorR = mMinorRadius;
4463 const double u0 = mPhiStart, u1 = mPhiStart + mPhiSweep;
4464 const double v0 = mTubeStart,
v1 = mTubeStart + mTubeSweep;
4465 const double centerU =
dot(mCenter, mAxisU);
4466 const double centerV =
dot(mCenter, mAxisV);
4467 const double centerW =
dot(mCenter, mAxisW);
4468 const double deltaU = u1 - u0;
4469 const double deltaV =
v1 -
v0;
4470 const double sinIntegralU = std::sin(u1) - std::sin(u0);
4471 const double cosIntegralU = std::cos(u0) - std::cos(u1);
4472 const double sinIntegralV = std::sin(
v1) - std::sin(
v0);
4473 const double sinFromCosV = std::cos(
v0) - std::cos(
v1);
4474 const double cosSquaredV = 0.5 * deltaV + 0.25 * (std::sin(2. *
v1) - std::sin(2. *
v0));
4475 const double sinCosV = 0.25 * (std::cos(2. *
v0) - std::cos(2. *
v1));
4478 const double centerlessV =
4479 minorR * ((majorR * majorR + minorR * minorR) * sinIntegralV + majorR * minorR * deltaV +
4480 majorR * minorR * cosSquaredV);
4482 const double centerWpart = minorR * (majorR * sinFromCosV + minorR * sinCosV);
4484 const double centerUVpart =
4485 (centerU * sinIntegralU + centerV * cosIntegralU) * minorR * (majorR * sinIntegralV + minorR * cosSquaredV);
4487 const double total = deltaU * centerlessV + deltaU * centerW * centerWpart + centerUVpart;
4488 return mNormalSign * total / 3.;
4496 const double outerRadius = mMajorRadius + mMinorRadius;
4497 for (
int dimension = 0; dimension < 3; ++dimension) {
4498 const double radialExtent = outerRadius * std::hypot(
component(mAxisU, dimension),
component(mAxisV, dimension)) +
4499 mMinorRadius * std::abs(
component(mAxisW, dimension));
4500 const double centerValue =
component(mCenter, dimension);
4501 if (dimension == 0) {
4502 lower.xCoord = std::min(
lower.xCoord, centerValue - radialExtent);
4503 upper.xCoord = std::max(
upper.xCoord, centerValue + radialExtent);
4504 }
else if (dimension == 1) {
4505 lower.yCoord = std::min(
lower.yCoord, centerValue - radialExtent);
4506 upper.yCoord = std::max(
upper.yCoord, centerValue + radialExtent);
4508 lower.zCoord = std::min(
lower.zCoord, centerValue - radialExtent);
4509 upper.zCoord = std::max(
upper.zCoord, centerValue + radialExtent);
4517 if (mMajorRadius < mMinorRadius) {
4523 for (
int ringChunk = 0; ringChunk < ringChunks; ++ringChunk) {
4524 const double ringLow =
kTwoPi * ringChunk / ringChunks;
4525 const double ringHigh =
kTwoPi * (ringChunk + 1) / ringChunks;
4526 for (
int tubeChunk = 0; tubeChunk < tubeChunks; ++tubeChunk) {
4527 const double tubeLow =
kTwoPi * tubeChunk / tubeChunks;
4528 const double tubeHigh =
kTwoPi * (tubeChunk + 1) / tubeChunks;
4531 for (
int dimension = 0; dimension < 3; ++dimension) {
4532 double inPlaneLow = 0.;
4533 double inPlaneHigh = 0.;
4538 const double axisComponent =
component(mAxisW, dimension);
4539 const double high =
sinusoidMaximum(inPlaneHigh, axisComponent, tubeLow, tubeHigh);
4540 const double low =
sinusoidMinimum(inPlaneLow, axisComponent, tubeLow, tubeHigh);
4541 lower[dimension] =
component(mCenter, dimension) + inPlaneLow * mMajorRadius + mMinorRadius * low;
4542 upper[dimension] =
component(mCenter, dimension) + inPlaneHigh * mMajorRadius + mMinorRadius * high;
4551 return std::max(1,
static_cast<int>(std::lround(
kArcSamples * mPhiSweep /
kTwoPi)));
4556 return std::max(1,
static_cast<int>(std::lround(
kArcSamples * mTubeSweep /
kTwoPi)));
4559 void appendDisplayMesh(std::vector<Vec3>& vertices, std::vector<std::array<int, 3>>& triangles)
const override
4562 appendCurveTrimMesh(mTrimOuter, [
this](
double phiRing,
double phiTube) {
return pointAt(phiRing, phiTube); }, vertices, triangles);
4567 const int firstVertexIndex =
static_cast<int>(vertices.size());
4568 for (
int ringStep = 0; ringStep <= ringSteps; ++ringStep) {
4569 const double phiRing = mPhiStart + mPhiSweep * ringStep / ringSteps;
4570 for (
int tubeStep = 0; tubeStep <= tubeSteps; ++tubeStep) {
4571 vertices.push_back(
pointAt(phiRing, mTubeStart + mTubeSweep * tubeStep / tubeSteps));
4574 const int rowLength = tubeSteps + 1;
4575 for (
int ringStep = 0; ringStep < ringSteps; ++ringStep) {
4576 for (
int tubeStep = 0; tubeStep < tubeSteps; ++tubeStep) {
4577 const int base = firstVertexIndex + ringStep * rowLength + tubeStep;
4578 triangles.push_back({base, base + rowLength, base + rowLength + 1});
4579 triangles.push_back({base, base + rowLength + 1, base + 1});
4594 auto emitEdge = [&](
const Vec3& edgeStart,
const Vec3& edgeEnd) {
4595 if (mNormalSign > 0.) {
4596 edges.emplace_back(edgeStart, edgeEnd);
4598 edges.emplace_back(edgeEnd, edgeStart);
4603 const double endRing = mPhiStart + mPhiSweep;
4604 const double endTube = mTubeStart + mTubeSweep;
4606 for (
int step = 0; step < ringSteps; ++step) {
4607 const double phiRing = mPhiStart + mPhiSweep * step / ringSteps;
4608 const double nextRing = mPhiStart + mPhiSweep * (step + 1) / ringSteps;
4609 emitEdge(
pointAt(phiRing, mTubeStart),
pointAt(nextRing, mTubeStart));
4614 for (
int step = 0; step < tubeSteps; ++step) {
4615 const double phiTube = mTubeStart + mTubeSweep * step / tubeSteps;
4616 const double nextTube = mTubeStart + mTubeSweep * (step + 1) / tubeSteps;
4618 emitEdge(
pointAt(mPhiStart, nextTube),
pointAt(mPhiStart, phiTube));
4625 if (!mHasWireTrim) {
4636 double mMajorRadius = 0.;
4637 double mMinorRadius = 0.;
4638 double mPhiStart = 0.;
4639 double mPhiSweep =
kTwoPi;
4640 double mTubeStart = 0.;
4641 double mTubeSweep =
kTwoPi;
4642 double mRingTolerance = 0.;
4643 double mTubeTolerance = 0.;
4644 double mNormalSign = 1.;
4645 bool mHasWireTrim =
false;
4647 std::vector<CurveWire> mTrimInner;
4734 std::map<uint32_t, std::vector<std::pair<int, size_t>>> claims;
4735 for (
size_t surfaceIndex = 0; surfaceIndex < surfaces.size(); ++surfaceIndex) {
4736 if (surfaces[surfaceIndex] ==
nullptr) {
4739 const auto& refs = surfaces[surfaceIndex]->boundaryEdges();
4740 for (
size_t slot = 0; slot < refs.size(); ++slot) {
4741 if (refs[slot].degenerate) {
4746 claims[refs[slot].edgeId].emplace_back(
static_cast<int>(surfaceIndex), slot);
4750 std::vector<Vec3>
first;
4751 std::vector<Vec3> second;
4752 for (
const auto& [edgeId, holders] : claims) {
4753 if (holders.size() != 2) {
4756 const auto& [firstSurface, firstSlot] = holders[0];
4757 const auto& [secondSurface, secondSlot] = holders[1];
4758 if (!surfaces[
static_cast<size_t>(firstSurface)]->sampleTrimCurve(firstSlot,
first) ||
4759 !surfaces[
static_cast<size_t>(secondSurface)]->sampleTrimCurve(secondSlot, second) ||
first.size() < 2 ||
4760 second.size() < 2) {
4761 ++
report.sharedEdgesUnmeasured;
4764 ++
report.sharedEdgesMeasured;
4765 auto worstAgainst = [](
const std::vector<Vec3>& probes,
const std::vector<Vec3>& polyline,
Vec3& where) {
4767 for (
const Vec3& probe : probes) {
4768 double nearest = std::numeric_limits<double>::infinity();
4772 if (nearest > worst) {
4777 return std::sqrt(worst);
4779 Vec3 forwardPoint{};
4780 Vec3 backwardPoint{};
4781 const double forwardWorst = worstAgainst(
first, second, forwardPoint);
4782 const double backwardWorst = worstAgainst(second,
first, backwardPoint);
4783 const double deviation = std::max(forwardWorst, backwardWorst);
4784 if (deviation >
report.maxSharedEdgeDeviation) {
4785 report.maxSharedEdgeDeviation = deviation;
4786 report.maxSharedEdgeDeviationEdge = edgeId;
4787 report.maxSharedEdgeDeviationPoint = forwardWorst >= backwardWorst ? forwardPoint : backwardPoint;
4788 report.maxSharedEdgeDeviationFaces[0] = firstSurface;
4789 report.maxSharedEdgeDeviationFaces[1] = secondSurface;
4795inline void measureRimClosure(
const std::vector<std::unique_ptr<BoundedSurface>>& surfaces,
double epsilon,
4798 report.rimEpsilon = epsilon;
4800 std::vector<SurfaceRim> rims;
4801 std::vector<int> rimIndexOnSurface;
4802 for (
size_t surfaceIndex = 0; surfaceIndex < surfaces.size(); ++surfaceIndex) {
4803 if (surfaces[surfaceIndex] ==
nullptr) {
4806 const size_t firstNewRim = rims.size();
4807 surfaces[surfaceIndex]->appendRims(rims);
4808 for (
size_t rimIndex = firstNewRim; rimIndex < rims.size(); ++rimIndex) {
4809 rims[rimIndex].surfaceIndex =
static_cast<int>(surfaceIndex);
4810 rimIndexOnSurface.push_back(
static_cast<int>(rimIndex - firstNewRim));
4813 report.rims =
static_cast<int>(rims.size());
4820 constexpr double kMaxSmoothTurn = 0.52;
4827 std::vector<Chord> chords;
4828 std::vector<std::pair<size_t, size_t>> chordRange(rims.size());
4829 for (
size_t rimIndex = 0; rimIndex < rims.size(); ++rimIndex) {
4831 chordRange[rimIndex].first = chords.size();
4832 const size_t pointCount = rim.
points.size();
4833 std::vector<double> vertexSagitta(pointCount, 0.);
4834 const size_t interiorCount = rim.
closed ? pointCount : (pointCount >= 2 ? pointCount - 2 : 0);
4837 const Vec3 incoming = rim.
points[middle] - rim.
points[(middle + pointCount - 1) % pointCount];
4838 const Vec3 outgoing = rim.
points[(middle + 1) % pointCount] - rim.
points[middle];
4839 const double incomingLength =
norm(incoming);
4840 const double outgoingLength =
norm(outgoing);
4844 const double turn = std::acos(std::clamp(
dot(incoming, outgoing) / (incomingLength * outgoingLength), -1., 1.));
4845 if (turn > kMaxSmoothTurn) {
4848 vertexSagitta[middle] = 0.25 * (incomingLength + outgoingLength) * std::tan(0.25 * turn);
4849 report.rimChordResolution = std::max(
report.rimChordResolution, vertexSagitta[middle]);
4851 const size_t chordCount = rim.
closed ? pointCount : pointCount - 1;
4852 for (
size_t pointIndex = 0; pointIndex < chordCount; ++pointIndex) {
4853 const size_t nextIndex = (pointIndex + 1) % pointCount;
4855 std::max(vertexSagitta[pointIndex], vertexSagitta[nextIndex])});
4857 chordRange[rimIndex].second = chords.size();
4859 if (chords.empty()) {
4865 auto grow = [&](
const Vec3& point) {
4866 lower = {std::min(
lower.xCoord, point.xCoord), std::min(
lower.yCoord, point.yCoord),
4867 std::min(
lower.zCoord, point.zCoord)};
4868 upper = {std::max(
upper.xCoord, point.xCoord), std::max(
upper.yCoord, point.yCoord),
4869 std::max(
upper.zCoord, point.zCoord)};
4871 for (
const Chord& chord : chords) {
4875 const int gridDimension =
4876 std::clamp(
static_cast<int>(std::cbrt(
static_cast<double>(chords.size()))), 1, 32);
4878 const double cellSize =
4880 auto cellOf = [&](
double coordinate,
double origin) {
4881 return std::clamp(
static_cast<int>((coordinate -
origin) / cellSize), 0, gridDimension - 1);
4883 auto cellIndex = [&](
int xCell,
int yCell,
int zCell) {
4884 return (xCell * gridDimension + yCell) * gridDimension + zCell;
4886 std::vector<std::vector<int>>
cells(
static_cast<size_t>(gridDimension) * gridDimension * gridDimension);
4887 for (
size_t chordIndex = 0; chordIndex < chords.size(); ++chordIndex) {
4888 const Chord& chord = chords[chordIndex];
4889 const int xLow = cellOf(std::min(chord.start.xCoord, chord.end.xCoord),
lower.xCoord);
4890 const int xHigh = cellOf(std::max(chord.start.xCoord, chord.end.xCoord),
lower.xCoord);
4891 const int yLow = cellOf(std::min(chord.start.yCoord, chord.end.yCoord),
lower.yCoord);
4892 const int yHigh = cellOf(std::max(chord.start.yCoord, chord.end.yCoord),
lower.yCoord);
4893 const int zLow = cellOf(std::min(chord.start.zCoord, chord.end.zCoord),
lower.zCoord);
4894 const int zHigh = cellOf(std::max(chord.start.zCoord, chord.end.zCoord),
lower.zCoord);
4895 for (
int xCell = xLow; xCell <= xHigh; ++xCell) {
4896 for (
int yCell = yLow; yCell <= yHigh; ++yCell) {
4897 for (
int zCell = zLow; zCell <= zHigh; ++zCell) {
4898 cells[cellIndex(xCell, yCell, zCell)].push_back(
static_cast<int>(chordIndex));
4905 double distance = std::numeric_limits<double>::infinity();
4906 int chordIndex = -1;
4908 bool withinBand =
false;
4911 std::array<int, 3> coincidentFaces{-1, -1, -1};
4912 int coincidentFaceCount = 0;
4915 const double maxBand = epsilon + 2. *
report.rimChordResolution;
4916 auto nearestOtherFace = [&](
const Vec3& probe,
int ownSurfaceIndex,
double probeResolution) {
4918 auto consider = [&](
int chordIndex) {
4919 const Chord& chord = chords[
static_cast<size_t>(chordIndex)];
4920 if (chord.surfaceIndex == ownSurfaceIndex) {
4926 match.chordIndex = chordIndex;
4928 if (
distance <= epsilon + probeResolution + chord.resolution) {
4929 match.withinBand =
true;
4931 if (
distance <= epsilon &&
match.coincidentFaceCount <
static_cast<int>(
match.coincidentFaces.size())) {
4932 for (
int seen = 0; seen <
match.coincidentFaceCount; ++seen) {
4933 if (
match.coincidentFaces[
static_cast<size_t>(seen)] == chord.surfaceIndex) {
4937 match.coincidentFaces[
static_cast<size_t>(
match.coincidentFaceCount++)] = chord.surfaceIndex;
4940 const int xCentre = cellOf(probe.
xCoord,
lower.xCoord);
4941 const int yCentre = cellOf(probe.
yCoord,
lower.yCoord);
4942 const int zCentre = cellOf(probe.
zCoord,
lower.zCoord);
4943 for (
int shell = 0; shell < gridDimension; ++shell) {
4945 const double shellReach = (shell - 1) * cellSize;
4946 if (shell > 0 && shellReach > std::max(
match.distance, maxBand)) {
4949 for (
int xCell = xCentre - shell; xCell <= xCentre + shell; ++xCell) {
4950 if (xCell < 0 || xCell >= gridDimension) {
4953 for (
int yCell = yCentre - shell; yCell <= yCentre + shell; ++yCell) {
4954 if (yCell < 0 || yCell >= gridDimension) {
4957 for (
int zCell = zCentre - shell; zCell <= zCentre + shell; ++zCell) {
4958 if (zCell < 0 || zCell >= gridDimension) {
4961 const bool onShell = std::abs(xCell - xCentre) == shell || std::abs(yCell - yCentre) == shell ||
4962 std::abs(zCell - zCentre) == shell;
4966 for (
const int chordIndex :
cells[cellIndex(xCell, yCell, zCell)]) {
4967 consider(chordIndex);
4976 report.rimRecords.reserve(rims.size());
4977 for (
size_t rimIndex = 0; rimIndex < rims.size(); ++rimIndex) {
4978 bool hasUnmatched =
false;
4979 bool hasNonManifold =
false;
4980 int sameDirectionVotes = 0;
4981 int oppositeDirectionVotes = 0;
4985 record.
closed = rims[rimIndex].closed;
4986 record.
chords =
static_cast<int>(chordRange[rimIndex].second - chordRange[rimIndex].first);
4987 for (
size_t chordIndex = chordRange[rimIndex].
first; chordIndex < chordRange[rimIndex].second; ++chordIndex) {
4988 const Chord& chord = chords[chordIndex];
4989 const Vec3 along = chord.end - chord.start;
4990 const double chordLength =
norm(along);
4991 report.totalRimLength += chordLength;
4992 record.
length += chordLength;
4993 const Vec3 probe = chord.start + along * 0.5;
4994 const Match
match = nearestOtherFace(probe, chord.surfaceIndex, chord.resolution);
4995 if (std::isfinite(
match.distance)) {
5003 if (
match.coincidentFaceCount > 1) {
5004 hasNonManifold =
true;
5006 if (!
match.withinBand) {
5007 hasUnmatched =
true;
5009 report.unmatchedRimLength += chordLength;
5013 const Chord& partner = chords[
static_cast<size_t>(
match.chordIndex)];
5014 if (
dot(along, partner.end - partner.start) < 0.) {
5015 ++oppositeDirectionVotes;
5017 ++sameDirectionVotes;
5020 if (hasNonManifold) {
5021 ++
report.nonManifoldRims;
5023 }
else if (hasUnmatched) {
5026 }
else if (sameDirectionVotes > oppositeDirectionVotes) {
5033 report.rimRecords.push_back(record);
5042 size_t surfacesPresent = 0;
5043 size_t surfacesStatingEdges = 0;
5044 for (
const auto& surface : surfaces) {
5045 if (surface ==
nullptr) {
5049 if (!surface->boundaryEdges().empty()) {
5050 ++surfacesStatingEdges;
5053 if (surfacesPresent == 0 || surfacesStatingEdges != surfacesPresent) {
5056 report.edgeIdentityAvailable =
true;
5063 std::map<uint32_t, Incidence> incidences;
5065 std::map<uint32_t, std::vector<int>> owners;
5066 for (
size_t surfaceIndex = 0; surfaceIndex < surfaces.size(); ++surfaceIndex) {
5067 if (surfaces[surfaceIndex] ==
nullptr) {
5070 for (
const auto&
ref : surfaces[surfaceIndex]->boundaryEdges()) {
5071 Incidence& incidence = incidences[
ref.edgeId];
5072 if (
ref.degenerate) {
5073 ++incidence.degenerate;
5074 }
else if (
ref.reversed) {
5075 ++incidence.reversed;
5077 ++incidence.forward;
5079 owners[
ref.edgeId].push_back(
static_cast<int>(surfaceIndex));
5100 if (rank(candidate) > rank(
state)) {
5105 for (
const auto& [edgeId, incidence] : incidences) {
5107 if (incidence.degenerate > 0 && incidence.forward + incidence.reversed == 0) {
5108 ++
report.edgeDegenerateCount;
5111 const int total = incidence.forward + incidence.reversed;
5114 ++
report.edgeBoundaryCount;
5116 }
else if (total == 2) {
5117 if (incidence.forward == 1 && incidence.reversed == 1) {
5118 ++
report.edgeSharedCount;
5120 ++
report.edgeReversedCount;
5124 ++
report.edgeNonManifoldCount;
5128 for (
const int owner : owners[edgeId]) {
5129 worsen(faceState[
static_cast<size_t>(owner)],
state);
5134 report.closed = (
report.edgeBoundaryCount == 0) && (
report.edgeNonManifoldCount == 0);
5135 report.orientationConsistent = (
report.edgeReversedCount == 0);
5139 report.nonManifoldRims = 0;
5142 const RimState state = record.surfaceIndex >= 0 && record.surfaceIndex <
static_cast<int>(faceState.size())
5143 ? faceState[
static_cast<size_t>(record.surfaceIndex)]
5145 record.state =
state;
5148 ++
report.nonManifoldRims;
5167 double modelTolerance = 0.)
5172 using VertexKey = std::tuple<int64_t, int64_t, int64_t>;
5173 auto keyOf = [&](
const Vec3& point) {
5174 return VertexKey{quantize(point.xCoord), quantize(point.yCoord), quantize(point.zCoord)};
5177 std::vector<std::pair<Vec3, Vec3>> directedEdges;
5178 for (
const auto& surface : surfaces) {
5179 if (surface !=
nullptr) {
5180 surface->appendDirectedEdges(directedEdges);
5181 report.signedVolume += surface->capacityContribution();
5186 std::map<std::pair<VertexKey, VertexKey>, std::pair<int, int>> edgeCounts;
5187 for (
const auto& directedEdge : directedEdges) {
5188 const VertexKey startKey = keyOf(directedEdge.first);
5189 const VertexKey endKey = keyOf(directedEdge.second);
5190 if (startKey == endKey) {
5193 const bool forward = startKey < endKey;
5194 const auto orderedKey = forward ? std::make_pair(startKey, endKey) : std::make_pair(endKey, startKey);
5195 auto& counts = edgeCounts[orderedKey];
5203 for (
const auto& [edgeKey, counts] : edgeCounts) {
5204 const int total = counts.first + counts.second;
5207 }
else if (total == 2) {
5208 if (counts.first != 1 || counts.second != 1) {
5212 ++
report.nonManifoldEdges;
5220 report.orientationConsistent = (
report.reversedRims == 0);
header::DataOrigin origin
constexpr int p1()
constexpr to accelerate the coordinates changing
std::vector< SidecarEdge > edges
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
float sum(float s, o2::dcs::DataPointValue v)
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)
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)
double dot(const Vec3 &firstVector, const Vec3 &secondVector)
Vec3 operator+(const Vec3 &firstVector, const Vec3 &secondVector)
std::vector< std::array< int, 3 > > triangulateSimpleWire(const SurfaceWire &wire)
Ear-clipping triangulation of a simple (non-self-intersecting) parametric wire.
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.
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 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,...
double unwrapAngleInto(double angle, double uMin, double uMax)
Shift angle by whole turns to lie as close as possible to the window [uMin, uMax].
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,...
void appendCurveTrimMesh(const CurveWire &outerWire, 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 outer loop,...
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