| | | 1 | | //======================================================================= |
| | | 2 | | // BoxCylinderAnalyticFeatures.cs |
| | | 3 | | //======================================================================= |
| | | 4 | | // MIT License, Copyright (c) 2024-present David Oravsky (mrdav30) |
| | | 5 | | // See LICENSE file in the project root for full license information. |
| | | 6 | | //======================================================================= |
| | | 7 | | |
| | | 8 | | using System; |
| | | 9 | | using static FixedMathSharp.Geometry.CylinderContactAlgebra; |
| | | 10 | | |
| | | 11 | | namespace FixedMathSharp.Geometry; |
| | | 12 | | |
| | | 13 | | /// <summary> |
| | | 14 | | /// Exact rational support directions and vertex/rim separation certificates |
| | | 15 | | /// for a box and a finite cylinder. Coordinates stay in the authored box frame. |
| | | 16 | | /// </summary> |
| | | 17 | | /// <remarks> |
| | | 18 | | /// The support sphere splits at box-edge planes, the cylinder's side plane |
| | | 19 | | /// and its two poles. This owner handles their intersections and side arcs; |
| | | 20 | | /// BoxCylinderEdgeContacts handles the remaining edge/rim arcs. Inside an |
| | | 21 | | /// open vertex/rim patch, zero meridional curvature rules out a positive |
| | | 22 | | /// minimum. Its outward residual can still separate, so that certificate |
| | | 23 | | /// must not be omitted merely because it cannot supply penetration depth. |
| | | 24 | | /// </remarks> |
| | | 25 | | internal static class BoxCylinderAnalyticFeatures |
| | | 26 | | { |
| | | 27 | | private const int Words = ConvexContactCandidate.Words; |
| | | 28 | | |
| | | 29 | | internal static bool TryGetBest(in BoxCylinderGeometry geometry, |
| | | 30 | | Span<ulong> bestValues, Span<int> bestSigns, out int bestGapSign, |
| | | 31 | | out Vector3d localSupportSigns) |
| | | 32 | | { |
| | 1183 | 33 | | var selection = new Selection(bestValues, bestSigns); |
| | 1183 | 34 | | Span<ulong> direction = stackalloc ulong[3 * Words]; |
| | 1183 | 35 | | Span<int> directionSigns = stackalloc int[3]; |
| | 1183 | 36 | | WriteDirection(geometry.Axis, direction, directionSigns); |
| | 1183 | 37 | | KeepAxis(geometry, direction, directionSigns, ref selection); |
| | 9442 | 38 | | for (int axis = 0; axis < 3 && selection.GapSign >= 0; axis++) |
| | | 39 | | { |
| | 3538 | 40 | | WideAxis3 unit = Unit(axis); |
| | 3538 | 41 | | WriteDirection(unit, direction, directionSigns); |
| | 3538 | 42 | | KeepAxis(geometry, direction, directionSigns, ref selection); |
| | 3538 | 43 | | WriteDirection(WideAxis3.Cross(unit, geometry.Axis), direction, directionSigns); |
| | 3538 | 44 | | KeepAxis(geometry, direction, directionSigns, ref selection); |
| | | 45 | | } |
| | | 46 | | |
| | 1183 | 47 | | if (!geometry.Radius.IsZero) |
| | | 48 | | { |
| | | 49 | | // On a cylinder's side-normal plane, each box vertex contributes |
| | | 50 | | // a linear term plus a circle. Its radial direction and the plane's |
| | | 51 | | // box-edge boundaries exhaust the possible minima. |
| | 21062 | 52 | | for (int corner = 0; corner < 8 && selection.GapSign >= 0; corner++) |
| | | 53 | | { |
| | 9361 | 54 | | WideAxis3 offset = geometry.GetCapVertexOffset(corner, 0); |
| | 9361 | 55 | | ProjectPerpendicular(geometry.Axis, offset, direction, directionSigns); |
| | 9361 | 56 | | KeepAxis(geometry, direction, directionSigns, ref selection); |
| | 56152 | 57 | | for (int cap = -1; cap <= 1; cap += 2) |
| | | 58 | | { |
| | 18720 | 59 | | if (HasVertexRimSeparation(geometry, corner, cap)) |
| | | 60 | | { |
| | 5 | 61 | | RestoreRawGapDenominator(geometry, bestValues, direction); |
| | 5 | 62 | | bestGapSign = -1; |
| | 5 | 63 | | localSupportSigns = default; |
| | 5 | 64 | | return false; |
| | | 65 | | } |
| | | 66 | | } |
| | | 67 | | } |
| | | 68 | | |
| | | 69 | | // The edge quartic eliminates sqrt(Q) using K=q-p*t. K=0 is a |
| | | 70 | | // rational direction, not permission to divide by zero. Include |
| | | 71 | | // it here using the full support function, independent of a cone. |
| | | 72 | | // When p=q=0 the projected disk's two principal axes suffice. |
| | 9326 | 73 | | for (int edge = 0; edge < 3 && selection.GapSign >= 0; edge++) |
| | | 74 | | { |
| | 3493 | 75 | | int j = (edge + 1) % 3; |
| | 3493 | 76 | | int k = (edge + 2) % 3; |
| | 3493 | 77 | | Signed320 aj = BoxCylinderGeometry.GetComponent(geometry.Axis, j); |
| | 3493 | 78 | | Signed320 ak = BoxCylinderGeometry.GetComponent(geometry.Axis, k); |
| | 3493 | 79 | | WritePlaneDirection(j, k, aj, ak, direction, directionSigns); |
| | 3493 | 80 | | KeepAxis(geometry, direction, directionSigns, ref selection); |
| | 3493 | 81 | | WritePlaneDirection(j, k, WideArithmetic.Negate(ak), aj, direction, directionSigns); |
| | 3493 | 82 | | KeepAxis(geometry, direction, directionSigns, ref selection); |
| | 34870 | 83 | | for (int cone = 0; cone < 4 && selection.GapSign >= 0; cone++) |
| | | 84 | | { |
| | 13942 | 85 | | int corner = ((cone & 1) != 0 ? 1 << j : 0) |
| | 13942 | 86 | | | ((cone & 2) != 0 ? 1 << k : 0); |
| | 83652 | 87 | | for (int cap = -1; cap <= 1; cap += 2) |
| | | 88 | | { |
| | 27884 | 89 | | WideAxis3 c = geometry.GetCapVertexOffset(corner, cap); |
| | 27884 | 90 | | Signed320 cj = BoxCylinderGeometry.GetComponent(c, j); |
| | 27884 | 91 | | Signed320 ck = BoxCylinderGeometry.GetComponent(c, k); |
| | 27884 | 92 | | int sj = (cone & 1) != 0 ? 1 : -1; |
| | 27884 | 93 | | int sk = (cone & 2) != 0 ? 1 : -1; |
| | 27884 | 94 | | if (cj.Sign * sj * ck.Sign * sk > 0) |
| | | 95 | | { |
| | 22836 | 96 | | WritePlaneDirection(j, k, cj, ck, direction, directionSigns); |
| | 22836 | 97 | | KeepAxis(geometry, direction, directionSigns, ref selection); |
| | | 98 | | } |
| | | 99 | | } |
| | | 100 | | } |
| | | 101 | | } |
| | | 102 | | } |
| | 1178 | 103 | | RestoreRawGapDenominator(geometry, bestValues, direction); |
| | 1178 | 104 | | bestGapSign = selection.GapSign; |
| | 1178 | 105 | | localSupportSigns = selection.LocalSigns; |
| | 1178 | 106 | | return bestGapSign >= 0; |
| | | 107 | | } |
| | | 108 | | |
| | | 109 | | internal static bool IsEdgeConeDominated(in BoxCylinderGeometry geometry, |
| | | 110 | | int edge, int first, int second, int firstSign, int secondSign, int cap, |
| | | 111 | | in ConvexContactCandidate best) |
| | | 112 | | { |
| | 2464 | 113 | | int corner = (firstSign > 0 ? 1 << first : 0) | (secondSign > 0 ? 1 << second : 0); |
| | 2464 | 114 | | WideAxis3 offset = geometry.GetCapVertexOffset(corner, cap); |
| | 2464 | 115 | | Signed320 cu = BoxCylinderGeometry.GetComponent(offset, first); |
| | 2464 | 116 | | Signed320 cv = BoxCylinderGeometry.GetComponent(offset, second); |
| | 2464 | 117 | | if (cu.Sign * firstSign < 0 || cv.Sign * secondSign < 0) |
| | 572 | 118 | | return false; |
| | | 119 | | |
| | | 120 | | // In this signed unit quadrant, cu*nu+cv*nv >= min(cu,cv). |
| | | 121 | | // The projected disk support is at least R*|a_edge|/sqrt(U). |
| | | 122 | | // Their sum bounds the entire cone, including both parameter charts. |
| | | 123 | | // It is positive here: radius and the edge's axis component are nonzero. |
| | 1892 | 124 | | Span<ulong> values = stackalloc ulong[ConvexContactCandidate.Slots * Words]; |
| | 1892 | 125 | | Span<int> signs = stackalloc int[ConvexContactCandidate.Slots]; |
| | 3784 | 126 | | values.Clear(); signs.Clear(); |
| | 3784 | 127 | | values[0] = 1; signs[0] = 1; // Comparison-only candidate: arbitrary unit normal. |
| | 1892 | 128 | | Span<ulong> work = stackalloc ulong[4 * Words]; |
| | 5676 | 129 | | Span<ulong> m = Slot(work, 0), other = Slot(work, 1), re = Slot(work, 2); |
| | 1892 | 130 | | Span<ulong> product = Slot(work, 3); |
| | 3784 | 131 | | Import(cu, m); Import(cv, other); |
| | 1892 | 132 | | if (WideArithmetic.CompareMagnitudeEqualLength(m, other) > 0) |
| | 935 | 133 | | other.CopyTo(m); |
| | 1892 | 134 | | Import(geometry.Radius, other); |
| | 1892 | 135 | | Import(BoxCylinderGeometry.GetComponent(geometry.Axis, edge), product); |
| | 1892 | 136 | | WideArithmetic.MultiplyMagnitudes(other, product, re); |
| | 1892 | 137 | | Import(geometry.Axis.SquaredLength, Slot(values, 9)); |
| | | 138 | | // b²=(A+B*sqrt(U))/D, A=m²U+(Re)², B=2mRe, D=S²U. |
| | | 139 | | // Conservative geometry bounds put A below 817 bits and D below 682. |
| | 1892 | 140 | | WideArithmetic.MultiplyMagnitudes(m, m, product); |
| | 1892 | 141 | | WideArithmetic.MultiplyMagnitudes(product, Slot(values, 9), Slot(values, 7)); |
| | 1892 | 142 | | WideArithmetic.MultiplyMagnitudes(re, re, product); |
| | 1892 | 143 | | WideArithmetic.AddMagnitudeInto(product, Slot(values, 7)); |
| | 1892 | 144 | | WideArithmetic.MultiplyMagnitudes(m, re, product); |
| | 1892 | 145 | | WideArithmetic.AddEqualMagnitudes(product, product, Slot(values, 8)); |
| | 1892 | 146 | | Import(Signed320.ExtendValue(geometry.RawScale), other); |
| | 1892 | 147 | | WideArithmetic.MultiplyMagnitudes(other, other, product); |
| | 1892 | 148 | | WideArithmetic.MultiplyMagnitudes(product, Slot(values, 9), Slot(values, 10)); |
| | 1892 | 149 | | signs[7] = 1; |
| | 1892 | 150 | | signs[8] = IsZero(m) ? 0 : 1; |
| | 1892 | 151 | | var bound = new ConvexContactCandidate(values, signs, 1); |
| | 1892 | 152 | | return WideConvexPrismRelations.CompareConvexContactCandidates(bound, best) >= 0; |
| | | 153 | | } |
| | | 154 | | |
| | | 155 | | private static void KeepAxis(in BoxCylinderGeometry geometry, |
| | | 156 | | scoped ReadOnlySpan<ulong> direction, scoped ReadOnlySpan<int> directionSigns, ref Selection selection) |
| | | 157 | | { |
| | 47442 | 158 | | if (selection.HasValue && selection.GapSign < 0) |
| | 9 | 159 | | return; |
| | 47433 | 160 | | if (directionSigns[0] == 0 && directionSigns[1] == 0 && directionSigns[2] == 0) |
| | 3129 | 161 | | return; |
| | 44304 | 162 | | Span<ulong> values = stackalloc ulong[ConvexContactCandidate.Slots * Words]; |
| | 44304 | 163 | | Span<int> signs = stackalloc int[ConvexContactCandidate.Slots]; |
| | 44304 | 164 | | BuildRankingAxis(geometry, direction, directionSigns, values, signs, out int gapSign, out int orientation); |
| | 44304 | 165 | | var candidate = new ConvexContactCandidate(values, signs, gapSign); |
| | 44304 | 166 | | if (selection.HasValue && WideConvexPrismRelations.CompareConvexContactCandidates(candidate, |
| | 44304 | 167 | | new ConvexContactCandidate(selection.Values, selection.Signs, selection.GapSign)) >= 0) |
| | 42769 | 168 | | return; |
| | 1535 | 169 | | values.CopyTo(selection.Values); |
| | 1535 | 170 | | signs.CopyTo(selection.Signs); |
| | 1535 | 171 | | selection.GapSign = gapSign; |
| | 1535 | 172 | | selection.HasValue = true; |
| | 1535 | 173 | | selection.LocalSigns = new Vector3d((Fixed64)(orientation * directionSigns[0]), |
| | 1535 | 174 | | (Fixed64)(orientation * directionSigns[1]), (Fixed64)(orientation * directionSigns[2])); |
| | 1535 | 175 | | } |
| | | 176 | | |
| | | 177 | | private static void BuildRankingAxis(in BoxCylinderGeometry geometry, |
| | | 178 | | ReadOnlySpan<ulong> direction, ReadOnlySpan<int> directionSigns, |
| | | 179 | | Span<ulong> values, Span<int> signs, out int gapSign, out int orientation) |
| | | 180 | | { |
| | 44304 | 181 | | values.Clear(); |
| | 44304 | 182 | | signs.Clear(); |
| | 44304 | 183 | | Span<ulong> work = stackalloc ulong[9 * Words]; |
| | 132912 | 184 | | Span<ulong> u = Slot(work, 0), n2 = Slot(work, 1), axial = Slot(work, 2); |
| | 132912 | 185 | | Span<ulong> rational = Slot(work, 3), temporary = Slot(work, 4), product = Slot(work, 5); |
| | 88608 | 186 | | Span<ulong> p = Slot(work, 6), q = Slot(work, 7); |
| | 44304 | 187 | | Span<ulong> radius = Slot(work, 8); |
| | 44304 | 188 | | Import(geometry.Axis.SquaredLength, u); |
| | 44304 | 189 | | SumSquares(direction, n2); |
| | 44304 | 190 | | Dot(geometry.CenterDifference, direction, directionSigns, rational, out int centerSign); |
| | 44304 | 191 | | orientation = centerSign < 0 ? -1 : 1; |
| | 44304 | 192 | | int rationalSign = IsZero(rational) ? 0 : -1; |
| | 44304 | 193 | | Dot(geometry.Half, direction, directionSigns, temporary, out _); |
| | 44304 | 194 | | Add(temporary, 1, rational, ref rationalSign); |
| | 354432 | 195 | | for (int component = 0; component < 3; component++) |
| | | 196 | | { |
| | 132912 | 197 | | Import(BoxCylinderGeometry.GetComponent(geometry.HalfExtents, component), temporary); |
| | 132912 | 198 | | WideArithmetic.MultiplyMagnitudes(temporary, Slot(direction, component), product); |
| | 132912 | 199 | | Add(product, 1, rational, ref rationalSign); |
| | | 200 | | } |
| | 44304 | 201 | | if (IsZero(rational)) |
| | 51 | 202 | | rationalSign = 0; |
| | | 203 | | |
| | | 204 | | // T=h_box(n)+|H.n|-|d.n|, U=a², V=U*n²-(a.n)² >= 0. |
| | | 205 | | // g²=(U*T²+R²*V+2*T*R*sqrt(U*V))/(U*S²*n²). Cancel U |
| | | 206 | | // before construction instead of carrying it through every ranking |
| | | 207 | | // product; R>=0 permits moving it outside the radical exactly. |
| | | 208 | | // Directions <2^577 and coordinates <2^237 give |T|<2^817, |
| | | 209 | | // U<2^342, R<2^235, S<2^170, V<2^1498 give A<2^1977, |
| | | 210 | | // B<2^1053, C<2^1840 and D<2^1838. All fit forty words, |
| | | 211 | | // including coefficient-sum carries. |
| | | 212 | | // All directions share positive U*S². Rank with denominator n², |
| | | 213 | | // preserving signed order and exact ties; restore raw units once |
| | | 214 | | // before either TryGetBest exit. These private scaled values never |
| | | 215 | | // enter cone pruning, edge-root ranking or public depth rounding. |
| | 44304 | 216 | | Dot(geometry.Axis, direction, directionSigns, axial, out _); |
| | 44304 | 217 | | WideArithmetic.MultiplyMagnitudes(axial, axial, temporary); |
| | 44304 | 218 | | WideArithmetic.MultiplyMagnitudes(u, n2, product); |
| | 44304 | 219 | | WideArithmetic.SubtractEqualMagnitudes(product, temporary, q); |
| | 44304 | 220 | | Import(geometry.Radius, radius); |
| | 44304 | 221 | | WideArithmetic.MultiplyMagnitudes(radius, radius, temporary); |
| | 44304 | 222 | | WideArithmetic.MultiplyMagnitudes(temporary, q, product); |
| | 44304 | 223 | | WideArithmetic.MultiplyMagnitudes(rational, rational, p); |
| | 44304 | 224 | | WideArithmetic.MultiplyMagnitudes(p, u, temporary); |
| | 44304 | 225 | | WideArithmetic.AddEqualMagnitudes(temporary, product, Slot(values, 7)); |
| | 44304 | 226 | | gapSign = rationalSign < 0 ? WideArithmetic.CompareMagnitudeEqualLength(product, temporary) |
| | 44304 | 227 | | : rationalSign > 0 || !IsZero(product) ? 1 : 0; |
| | 44304 | 228 | | WideArithmetic.MultiplyMagnitudes(rational, radius, p); |
| | 44304 | 229 | | WideArithmetic.AddEqualMagnitudes(p, p, Slot(values, 8)); |
| | 44304 | 230 | | WideArithmetic.MultiplyMagnitudes(u, q, Slot(values, 9)); |
| | 44304 | 231 | | n2.CopyTo(Slot(values, 10)); |
| | 44304 | 232 | | signs[7] = IsZero(Slot(values, 7)) ? 0 : 1; |
| | 44304 | 233 | | signs[8] = IsZero(p) ? 0 : rationalSign; |
| | 44304 | 234 | | CylinderContactAlgebra.WriteWorldDirection(geometry.WorldBasis, direction, |
| | 44304 | 235 | | directionSigns, values, signs, orientation); |
| | 44304 | 236 | | } |
| | | 237 | | |
| | | 238 | | private static void RestoreRawGapDenominator(in BoxCylinderGeometry geometry, |
| | | 239 | | Span<ulong> values, Span<ulong> scratch) |
| | | 240 | | { |
| | | 241 | | // The direction workspace is dead at either exit. Reuse its three |
| | | 242 | | // slots to reconstruct the original D=U*S²*n² (<2^1838). |
| | 3549 | 243 | | Span<ulong> u = Slot(scratch, 0), scale = Slot(scratch, 1), product = Slot(scratch, 2); |
| | 1183 | 244 | | Import(geometry.Axis.SquaredLength, u); |
| | 1183 | 245 | | Import(Signed320.ExtendValue(geometry.RawScale), scale); |
| | 1183 | 246 | | WideArithmetic.MultiplyMagnitudes(scale, scale, product); |
| | 1183 | 247 | | WideArithmetic.MultiplyMagnitudes(u, product, scale); |
| | 1183 | 248 | | WideArithmetic.MultiplyMagnitudes(Slot(values, 10), scale, product); |
| | 1183 | 249 | | product.CopyTo(Slot(values, 10)); |
| | 1183 | 250 | | } |
| | | 251 | | |
| | | 252 | | private static bool HasVertexRimSeparation(in BoxCylinderGeometry geometry, int corner, int cap) |
| | | 253 | | { |
| | 18720 | 254 | | WideAxis3 c = geometry.GetCapVertexOffset(corner, cap); |
| | 18720 | 255 | | Signed576 axial = WideAxis3.Dot(geometry.Axis, c); |
| | 18720 | 256 | | if (axial.Sign * cap > 0) |
| | 13838 | 257 | | return false; |
| | 4882 | 258 | | Span<ulong> work = stackalloc ulong[10 * Words]; |
| | 14646 | 259 | | Span<ulong> u = Slot(work, 0), q = Slot(work, 1), radius = Slot(work, 2); |
| | 14646 | 260 | | Span<ulong> first = Slot(work, 3), second = Slot(work, 4), root = Slot(work, 5); |
| | 9764 | 261 | | Span<ulong> radial = work.Slice(6 * Words, 3 * Words), coordinate = Slot(work, 9); |
| | 4882 | 262 | | Span<int> radialSigns = stackalloc int[3]; |
| | 4882 | 263 | | Import(geometry.Axis.SquaredLength, u); |
| | 4882 | 264 | | Import(c.SquaredLength, first); |
| | 4882 | 265 | | WideArithmetic.MultiplyMagnitudes(u, first, q); |
| | 4882 | 266 | | Import(axial, first); |
| | 4882 | 267 | | WideArithmetic.MultiplyMagnitudes(first, first, second); |
| | 4882 | 268 | | WideArithmetic.SubtractEqualMagnitudes(q, second, q); |
| | 4882 | 269 | | if (IsZero(q)) |
| | 4 | 270 | | return false; // A cap pole already supplies this normal. |
| | 4878 | 271 | | Import(geometry.Radius, radius); |
| | 4878 | 272 | | WideArithmetic.MultiplyMagnitudes(radius, radius, first); |
| | 4878 | 273 | | WideArithmetic.MultiplyMagnitudes(first, u, second); |
| | 4878 | 274 | | int radialComparison = WideArithmetic.CompareMagnitudeEqualLength(q, second); |
| | | 275 | | // At equality the residual is purely axial (or zero). The cylinder |
| | | 276 | | // pole was tested first, including separation; this is not a new rim. |
| | 4878 | 277 | | if (radialComparison <= 0) |
| | 224 | 278 | | return false; |
| | 4654 | 279 | | WideArithmetic.MultiplyMagnitudes(u, q, root); |
| | 4654 | 280 | | ProjectPerpendicular(geometry.Axis, c, radial, radialSigns); |
| | | 281 | | // A smooth vertex/rim patch has zero meridional curvature. A positive |
| | | 282 | | // support gap therefore has a negative second variation there and |
| | | 283 | | // cannot be the minimum penetration. Only its outward residual can |
| | | 284 | | // separate: n=R*P_a(c)-c*sqrt(U*q). Test its exact support cone. |
| | 9522 | 285 | | for (int component = 0; component < 3; component++) |
| | | 286 | | { |
| | 4756 | 287 | | WideArithmetic.MultiplyMagnitudes(radius, Slot(radial, component), first); |
| | 4756 | 288 | | Signed320 value = BoxCylinderGeometry.GetComponent(c, component); |
| | 4756 | 289 | | Import(value, coordinate); |
| | 4756 | 290 | | int sign = WideConvexPrismRelations.GetConvexContactCandidateQuadraticSign( |
| | 4756 | 291 | | first, radialSigns[component], coordinate, -value.Sign, root); |
| | 4756 | 292 | | int supportSign = (corner & (1 << component)) != 0 ? 1 : -1; |
| | 4756 | 293 | | if (sign * supportSign < 0) |
| | 4649 | 294 | | return false; |
| | | 295 | | } |
| | 5 | 296 | | return true; |
| | | 297 | | } |
| | | 298 | | |
| | | 299 | | private static void WritePlaneDirection(int j, int k, Signed320 u, Signed320 v, |
| | | 300 | | Span<ulong> direction, Span<int> signs) |
| | | 301 | | { |
| | 29822 | 302 | | direction.Clear(); |
| | 29822 | 303 | | signs.Clear(); |
| | 59644 | 304 | | Import(u, Slot(direction, j)); signs[j] = u.Sign; |
| | 59644 | 305 | | Import(v, Slot(direction, k)); signs[k] = v.Sign; |
| | 29822 | 306 | | } |
| | | 307 | | |
| | 3538 | 308 | | private static WideAxis3 Unit(int axis) => new( |
| | 3538 | 309 | | Signed320.ExtendValue(Signed192.Signed(axis == 0 ? 1 : 0)), |
| | 3538 | 310 | | Signed320.ExtendValue(Signed192.Signed(axis == 1 ? 1 : 0)), |
| | 3538 | 311 | | Signed320.ExtendValue(Signed192.Signed(axis == 2 ? 1 : 0))); |
| | | 312 | | private ref struct Selection |
| | | 313 | | { |
| | | 314 | | internal Span<ulong> Values; |
| | | 315 | | internal Span<int> Signs; |
| | | 316 | | internal int GapSign; |
| | | 317 | | internal bool HasValue; |
| | | 318 | | internal Vector3d LocalSigns; |
| | | 319 | | internal Selection(Span<ulong> values, Span<int> signs) |
| | | 320 | | { |
| | 1183 | 321 | | Values = values; |
| | 1183 | 322 | | Signs = signs; |
| | 1183 | 323 | | GapSign = 0; |
| | 1183 | 324 | | HasValue = false; |
| | 1183 | 325 | | LocalSigns = default; |
| | 1183 | 326 | | } |
| | | 327 | | } |
| | | 328 | | } |