| | | 1 | | //======================================================================= |
| | | 2 | | // TriangleCylinderWitnesses.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>Selected triangle-feature witnesses, with one final coordinate rounding.</summary> |
| | | 14 | | internal static class TriangleCylinderWitnesses |
| | | 15 | | { |
| | | 16 | | /// <summary> |
| | | 17 | | /// Intersects the selected support feature with the radial tangent plane, |
| | | 18 | | /// then with the finite axial interval. No cap endpoint is used as a proxy |
| | | 19 | | /// for the free side segment, and barycentric parameters never round first. |
| | | 20 | | /// Shifted core-region coordinates are below 232 bits and analytic |
| | | 21 | | /// directions below 630. Tangent weights stay below 864 bits, axial |
| | | 22 | | /// numerators below 1098, and blended weights below 1964. Their coordinate |
| | | 23 | | /// products remain below 2200 bits, inside forty-word candidate storage. |
| | | 24 | | /// </summary> |
| | | 25 | | internal static bool TryGetSidePoint(in TriangleCircularGeometry geometry, FixedTriangle triangle, |
| | | 26 | | int mask, ReadOnlySpan<ulong> normal, ReadOnlySpan<int> normalSigns, |
| | | 27 | | out Vector3d point, out Fixed64 axial, out int cap) |
| | | 28 | | { |
| | 504 | 29 | | point = default; axial = default; cap = 0; |
| | 168 | 30 | | Span<ulong> tangent = stackalloc ulong[3 * Words]; |
| | 168 | 31 | | Span<int> tangentSigns = stackalloc int[3]; |
| | 336 | 32 | | tangent.Clear(); tangentSigns.Clear(); |
| | 336 | 33 | | Slot(normal, 2).CopyTo(Slot(tangent, 0)); tangentSigns[0] = -normalSigns[2]; |
| | 336 | 34 | | Slot(normal, 0).CopyTo(Slot(tangent, 2)); tangentSigns[2] = normalSigns[0]; |
| | 168 | 35 | | Span<ulong> distances = stackalloc ulong[3 * Words]; |
| | 168 | 36 | | Span<int> distanceSigns = stackalloc int[3]; |
| | 168 | 37 | | Span<ulong> candidates = stackalloc ulong[6 * Words]; |
| | 168 | 38 | | Span<ulong> axialValues = stackalloc ulong[2 * Words]; |
| | 168 | 39 | | Span<int> axialSigns = stackalloc int[2]; |
| | 168 | 40 | | candidates.Clear(); |
| | 1344 | 41 | | for (int vertex = 0; vertex < 3; vertex++) |
| | 504 | 42 | | Dot(geometry.Vertex(vertex), tangent, tangentSigns, Slot(distances, vertex), out distanceSigns[vertex]); |
| | 168 | 43 | | int count = 0; |
| | 1344 | 44 | | for (int vertex = 0; vertex < 3; vertex++) |
| | | 45 | | { |
| | 504 | 46 | | if ((mask & (1 << vertex)) != 0 && distanceSigns[vertex] == 0) |
| | 13 | 47 | | candidates[(count++ * 3 + vertex) * Words] = 1; |
| | | 48 | | } |
| | 1344 | 49 | | for (int edge = 0; edge < 3; edge++) |
| | | 50 | | { |
| | 504 | 51 | | int next = (edge + 1) % 3; |
| | 504 | 52 | | if ((mask & (1 << edge)) == 0 || (mask & (1 << next)) == 0 |
| | 504 | 53 | | || distanceSigns[edge] * distanceSigns[next] >= 0) |
| | | 54 | | continue; |
| | 290 | 55 | | Slot(distances, next).CopyTo(Slot(candidates, 3 * count + edge)); |
| | 290 | 56 | | Slot(distances, edge).CopyTo(Slot(candidates, 3 * count + next)); |
| | 290 | 57 | | count++; |
| | | 58 | | } |
| | 170 | 59 | | if (count == 0) return false; |
| | | 60 | | System.Diagnostics.Debug.Assert(count <= 2); |
| | 166 | 61 | | Span<ulong> weights = stackalloc ulong[3 * Words]; |
| | 166 | 62 | | Span<ulong> denominator = stackalloc ulong[Words]; |
| | 166 | 63 | | Span<ulong> bound = stackalloc ulong[Words]; |
| | 166 | 64 | | Span<ulong> half = stackalloc ulong[Words]; |
| | 166 | 65 | | Import(geometry.HalfHeight, half); |
| | 166 | 66 | | int selected = -1; |
| | 166 | 67 | | cap = 0; |
| | 464 | 68 | | for (int index = 0; index < count; index++) |
| | | 69 | | { |
| | 217 | 70 | | ReadOnlySpan<ulong> candidate = candidates.Slice(index * 3 * Words, 3 * Words); |
| | 217 | 71 | | SumWeights(candidate, denominator); |
| | 217 | 72 | | SumCoordinates(geometry, candidate, 1, Slot(axialValues, index), out axialSigns[index]); |
| | 217 | 73 | | WideArithmetic.MultiplyMagnitudes(denominator, half, bound); |
| | 217 | 74 | | int comparison = WideArithmetic.CompareMagnitudeEqualLength(Slot(axialValues, index), bound); |
| | 217 | 75 | | if (comparison <= 0) |
| | | 76 | | { |
| | 151 | 77 | | selected = index; |
| | 151 | 78 | | if (comparison == 0) |
| | 13 | 79 | | cap = -axialSigns[index]; |
| | 13 | 80 | | break; |
| | | 81 | | } |
| | | 82 | | } |
| | 166 | 83 | | if (selected >= 0) |
| | 151 | 84 | | candidates.Slice(selected * 3 * Words, 3 * Words).CopyTo(weights); |
| | | 85 | | else |
| | | 86 | | { |
| | | 87 | | // The minimizing side support proves an admitted axial point. |
| | | 88 | | // If neither endpoint is in the interval, they straddle it. Blend |
| | | 89 | | // directly at Y=0: w=w0*|Y1 numerator|+w1*|Y0 numerator|. |
| | 17 | 90 | | if (count != 2 || axialSigns[0] * axialSigns[1] >= 0) return false; |
| | 13 | 91 | | Span<ulong> product = stackalloc ulong[Words]; |
| | 104 | 92 | | for (int vertex = 0; vertex < 3; vertex++) |
| | | 93 | | { |
| | 39 | 94 | | WideArithmetic.MultiplyMagnitudes(Slot(candidates, vertex), Slot(axialValues, 1), Slot(weights, vertex)) |
| | 39 | 95 | | WideArithmetic.MultiplyMagnitudes(Slot(candidates, vertex + 3), Slot(axialValues, 0), product); |
| | 39 | 96 | | WideArithmetic.AddMagnitudeInto(product, Slot(weights, vertex)); |
| | | 97 | | } |
| | | 98 | | } |
| | 164 | 99 | | SumWeights(weights, denominator); |
| | 164 | 100 | | SumCoordinates(geometry, weights, 1, bound, out int axialSign); |
| | 164 | 101 | | Import(Signed576.ExtendValue(geometry.RawScale), half); |
| | 164 | 102 | | Span<ulong> scaledDenominator = stackalloc ulong[Words]; |
| | 164 | 103 | | WideArithmetic.MultiplyMagnitudes(denominator, half, scaledDenominator); |
| | 164 | 104 | | bool represented = Fixed64.TryGetSignedRawRatio(bound, scaledDenominator, axialSign < 0, out axial); |
| | | 105 | | System.Diagnostics.Debug.Assert(represented); |
| | 164 | 106 | | point = RoundTriangle(geometry, triangle, weights, denominator); |
| | 164 | 107 | | return true; |
| | | 108 | | } |
| | | 109 | | |
| | | 110 | | /// <summary> |
| | | 111 | | /// Projects the origin onto the selected cap-pole feature, retaining its exact barycentric |
| | | 112 | | /// weights until both authored and cylinder-local coordinates round. |
| | | 113 | | /// A cap-pole minimum proves the projected radial point is in the disk; |
| | | 114 | | /// its final-rounded coordinates must not be normalized a second time. |
| | | 115 | | /// </summary> |
| | | 116 | | internal static Vector3d GetCapPoint(in TriangleCircularGeometry geometry, FixedTriangle triangle, |
| | | 117 | | int mask, out Vector3d projectedRadial) |
| | | 118 | | { |
| | 68 | 119 | | bool admitted = TryGetCapPoint(geometry, triangle, mask, out Vector3d point, out projectedRadial, false); |
| | | 120 | | System.Diagnostics.Debug.Assert(admitted); |
| | 68 | 121 | | return point; |
| | | 122 | | } |
| | | 123 | | |
| | | 124 | | internal static bool TryGetCapPoint(in TriangleCircularGeometry geometry, FixedTriangle triangle, |
| | | 125 | | int mask, out Vector3d point, out Vector3d projectedRadial, bool requireAdmission = true) |
| | | 126 | | { |
| | 106 | 127 | | point = projectedRadial = default; |
| | 106 | 128 | | Span<ulong> weights = stackalloc ulong[3 * Words]; |
| | 106 | 129 | | Span<ulong> denominator = stackalloc ulong[Words]; |
| | 106 | 130 | | GetCapWeights(geometry, mask, weights); |
| | 106 | 131 | | SumWeights(weights, denominator); |
| | 106 | 132 | | if (requireAdmission) |
| | | 133 | | { |
| | 38 | 134 | | Span<ulong> work = stackalloc ulong[3 * Words]; |
| | 38 | 135 | | SumCoordinates(geometry, weights, 0, Slot(work, 0), out _); |
| | 38 | 136 | | SumCoordinates(geometry, weights, 2, Slot(work, 1), out _); |
| | 38 | 137 | | Span<ulong> radial = stackalloc ulong[Words]; |
| | 38 | 138 | | Import(geometry.Radius, radial); |
| | 38 | 139 | | WideArithmetic.MultiplyMagnitudes(denominator, radial, Slot(work, 2)); |
| | | 140 | | // Affine barycentric numerators fit the existing bank; their |
| | | 141 | | // squares need complete products plus the radial-sum carry. |
| | 38 | 142 | | int squareWords = 2 * Math.Max(WideArithmetic.GetActiveMagnitudeLength(Slot(work, 0)), |
| | 38 | 143 | | Math.Max(WideArithmetic.GetActiveMagnitudeLength(Slot(work, 1)), WideArithmetic.GetActiveMagnitudeLength |
| | 38 | 144 | | Span<ulong> squares = stackalloc ulong[3 * squareWords]; |
| | 114 | 145 | | Span<ulong> sum = squares[..squareWords], term = squares.Slice(squareWords, squareWords), limit = squares.Sl |
| | 38 | 146 | | WideArithmetic.MultiplyMagnitudes(Slot(work, 0), Slot(work, 0), sum); |
| | 38 | 147 | | WideArithmetic.MultiplyMagnitudes(Slot(work, 1), Slot(work, 1), term); |
| | 38 | 148 | | WideArithmetic.AddMagnitudeInto(term, sum); |
| | 38 | 149 | | WideArithmetic.MultiplyMagnitudes(Slot(work, 2), Slot(work, 2), limit); |
| | 42 | 150 | | if (WideArithmetic.CompareMagnitudeEqualLength(sum, limit) > 0) return false; |
| | | 151 | | } |
| | 102 | 152 | | point = RoundTriangle(geometry, triangle, weights, denominator); |
| | 102 | 153 | | Span<ulong> scale = stackalloc ulong[Words]; |
| | 102 | 154 | | Span<ulong> scaledDenominator = stackalloc ulong[Words]; |
| | 102 | 155 | | Span<ulong> coordinate = stackalloc ulong[Words]; |
| | 102 | 156 | | Import(Signed576.ExtendValue(geometry.RawScale), scale); |
| | 102 | 157 | | WideArithmetic.MultiplyMagnitudes(denominator, scale, scaledDenominator); |
| | 102 | 158 | | SumCoordinates(geometry, weights, 0, coordinate, out int sign); |
| | 102 | 159 | | bool representedX = Fixed64.TryGetSignedRawRatio(coordinate, scaledDenominator, sign < 0, out Fixed64 x); |
| | 102 | 160 | | SumCoordinates(geometry, weights, 2, coordinate, out sign); |
| | 102 | 161 | | bool representedZ = Fixed64.TryGetSignedRawRatio(coordinate, scaledDenominator, sign < 0, out Fixed64 z); |
| | | 162 | | System.Diagnostics.Debug.Assert(representedX && representedZ); // Both bounded by the authored radius. |
| | 102 | 163 | | projectedRadial = new Vector3d(x, Fixed64.Zero, z); |
| | 102 | 164 | | return true; |
| | | 165 | | } |
| | | 166 | | |
| | | 167 | | internal static void GetCapWeights(in TriangleCircularGeometry geometry, int mask, Span<ulong> weights) |
| | | 168 | | { |
| | 123 | 169 | | weights.Clear(); |
| | 123 | 170 | | if (mask == 7) |
| | 97 | 171 | | GetFaceWeights(geometry, weights); |
| | 26 | 172 | | else if ((mask & (mask - 1)) == 0) |
| | 19 | 173 | | weights[((mask & 1) != 0 ? 0 : (mask & 2) != 0 ? 1 : 2) * Words] = 1; |
| | | 174 | | else |
| | 7 | 175 | | GetEdgeWeights(geometry, mask, weights); |
| | 7 | 176 | | } |
| | | 177 | | |
| | | 178 | | private static void GetEdgeWeights(in TriangleCircularGeometry geometry, int mask, Span<ulong> weights) |
| | | 179 | | { |
| | 7 | 180 | | int first = (mask & 1) != 0 ? 0 : 1; |
| | 7 | 181 | | int second = (mask & 4) != 0 ? 2 : 1; |
| | 7 | 182 | | WideDirection3d edge = geometry.EdgeFromTo(first, second); |
| | | 183 | | // A pole's selected feature has constant local Y. Projecting the |
| | | 184 | | // origin is identical to projecting either cap center onto it. |
| | 7 | 185 | | Signed832 parameter = WideDirection3d.Dot(TriangleCircularGeometry.Subtract(default, geometry.Vertex(first)), ed |
| | 7 | 186 | | Signed832 denominatorValue = WideArithmetic.MultiplySigned832(edge.SquaredLength, geometry.EdgeScale); |
| | 7 | 187 | | if (parameter.Sign <= 0) |
| | | 188 | | { |
| | 2 | 189 | | weights[first * Words] = 1; |
| | 2 | 190 | | return; |
| | | 191 | | } |
| | 5 | 192 | | Signed832 remainder = WideArithmetic.SubtractSigned832(denominatorValue, parameter); |
| | 5 | 193 | | if (remainder.Sign <= 0) |
| | | 194 | | { |
| | 1 | 195 | | weights[second * Words] = 1; |
| | 1 | 196 | | return; |
| | | 197 | | } |
| | 8 | 198 | | Import(remainder, Slot(weights, first)); Import(parameter, Slot(weights, second)); |
| | 4 | 199 | | } |
| | | 200 | | |
| | | 201 | | private static void GetFaceWeights(in TriangleCircularGeometry geometry, Span<ulong> weights) |
| | | 202 | | { |
| | 194 | 203 | | WideDirection3d e = geometry.Edge(0), f = -geometry.Edge(2); |
| | 97 | 204 | | WideDirection3d offset = TriangleCircularGeometry.Subtract(default, geometry.A); |
| | 194 | 205 | | Signed832 d1 = WideDirection3d.Dot(e, offset), d2 = WideDirection3d.Dot(f, offset); |
| | 97 | 206 | | if (d1.Sign <= 0 && d2.Sign <= 0) |
| | | 207 | | { |
| | 8 | 208 | | weights[0] = 1; |
| | 8 | 209 | | return; |
| | | 210 | | } |
| | 267 | 211 | | Signed832 ee = e.SquaredLength, ff = f.SquaredLength, ef = WideDirection3d.Dot(e, f); |
| | 89 | 212 | | Signed832 d3 = WideArithmetic.SubtractSigned832(d1, WideArithmetic.MultiplySigned832(ee, geometry.EdgeScale)); |
| | 89 | 213 | | Signed832 d4 = WideArithmetic.SubtractSigned832(d2, WideArithmetic.MultiplySigned832(ef, geometry.EdgeScale)); |
| | 89 | 214 | | if (d3.Sign >= 0 && WideArithmetic.SubtractSigned832(d4, d3).Sign <= 0) |
| | | 215 | | { |
| | 3 | 216 | | weights[Words] = 1; |
| | 3 | 217 | | return; |
| | | 218 | | } |
| | | 219 | | // Retained edges are <196 bits even for shifted core regions. Gram |
| | | 220 | | // dots are <394, offset dots <430; cancelled barycentric minors are |
| | | 221 | | // <825 and the EdgeScale-adjusted alpha <827, within Signed832. |
| | 86 | 222 | | Span<ulong> minors = stackalloc ulong[4 * Words]; |
| | 344 | 223 | | Span<ulong> vc = Slot(minors, 0), vb = Slot(minors, 1), va = Slot(minors, 2), determinant = Slot(minors, 3); |
| | 86 | 224 | | DifferenceOfProducts(ee, d2, ef, d1, vc, out int vcSign); |
| | 86 | 225 | | if (vcSign <= 0 && d1.Sign >= 0 && d3.Sign <= 0) |
| | | 226 | | { |
| | 10 | 227 | | SetEdgeWeights(0, 1, WideArithmetic.SubtractSigned832(default, d3), d1, weights); |
| | 10 | 228 | | return; |
| | | 229 | | } |
| | 76 | 230 | | Signed832 d5 = WideArithmetic.SubtractSigned832(d1, WideArithmetic.MultiplySigned832(ef, geometry.EdgeScale)); |
| | 76 | 231 | | Signed832 d6 = WideArithmetic.SubtractSigned832(d2, WideArithmetic.MultiplySigned832(ff, geometry.EdgeScale)); |
| | 76 | 232 | | if (d6.Sign >= 0 && WideArithmetic.SubtractSigned832(d5, d6).Sign <= 0) |
| | | 233 | | { |
| | 3 | 234 | | weights[2 * Words] = 1; |
| | 3 | 235 | | return; |
| | | 236 | | } |
| | 73 | 237 | | DifferenceOfProducts(ff, d1, ef, d2, vb, out int vbSign); |
| | | 238 | | // Once A/B/AB are excluded, vb<=0 proves d2>=0: the opposite |
| | | 239 | | // signs would force the earlier AB or B Voronoi region (Gram det>0). |
| | 73 | 240 | | if (vbSign <= 0 && d6.Sign <= 0) |
| | | 241 | | { |
| | 20 | 242 | | SetEdgeWeights(0, 2, WideArithmetic.SubtractSigned832(default, d6), d2, weights); |
| | 20 | 243 | | return; |
| | | 244 | | } |
| | 53 | 245 | | DifferenceOfProducts(ee, ff, ef, ef, determinant, out int vaSign); |
| | 53 | 246 | | Span<ulong> factor = stackalloc ulong[Words]; |
| | 53 | 247 | | Import(Signed576.ExtendValue(geometry.EdgeScale), factor); |
| | 53 | 248 | | WideArithmetic.MultiplyMagnitudes(determinant, factor, va); |
| | 106 | 249 | | Add(vb, -vbSign, va, ref vaSign); Add(vc, -vcSign, va, ref vaSign); |
| | 106 | 250 | | Signed832 first = WideArithmetic.SubtractSigned832(d4, d3), second = WideArithmetic.SubtractSigned832(d5, d6); |
| | | 251 | | // The preceding vertex and edge regions already prove both BC |
| | | 252 | | // weights nonnegative whenever va<=0 for this nondegenerate face. |
| | 53 | 253 | | if (vaSign <= 0) |
| | | 254 | | { |
| | 8 | 255 | | SetEdgeWeights(1, 2, second, first, weights); |
| | 8 | 256 | | return; |
| | | 257 | | } |
| | 135 | 258 | | va.CopyTo(Slot(weights, 0)); vb.CopyTo(Slot(weights, 1)); vc.CopyTo(Slot(weights, 2)); |
| | 45 | 259 | | } |
| | | 260 | | |
| | | 261 | | private static void SetEdgeWeights(int first, int second, |
| | | 262 | | Signed832 firstWeight, Signed832 secondWeight, Span<ulong> weights) |
| | | 263 | | { |
| | 76 | 264 | | Import(firstWeight, Slot(weights, first)); Import(secondWeight, Slot(weights, second)); |
| | 38 | 265 | | } |
| | | 266 | | |
| | | 267 | | private static Vector3d RoundTriangle(in TriangleCircularGeometry geometry, FixedTriangle triangle, ReadOnlySpan<ulo |
| | 266 | 268 | | ReadOnlySpan<ulong> denominator) => new( |
| | 266 | 269 | | RoundComponent(geometry, triangle, 0, weights, denominator), |
| | 266 | 270 | | RoundComponent(geometry, triangle, 1, weights, denominator), |
| | 266 | 271 | | RoundComponent(geometry, triangle, 2, weights, denominator)); |
| | | 272 | | |
| | | 273 | | private static Fixed64 RoundComponent(in TriangleCircularGeometry geometry, FixedTriangle triangle, int component, |
| | | 274 | | ReadOnlySpan<ulong> weights, ReadOnlySpan<ulong> denominator) |
| | | 275 | | { |
| | 798 | 276 | | Span<ulong> sum = stackalloc ulong[Words]; |
| | 798 | 277 | | Span<ulong> value = stackalloc ulong[Words]; |
| | 798 | 278 | | Span<ulong> product = stackalloc ulong[Words]; |
| | 1596 | 279 | | sum.Clear(); int sign = 0; |
| | 6384 | 280 | | for (int vertex = 0; vertex < 3; vertex++) |
| | | 281 | | { |
| | 2394 | 282 | | Signed576 coordinate = geometry.WitnessCoordinate(triangle, vertex, component); |
| | 2394 | 283 | | Import(coordinate, value); |
| | 2394 | 284 | | WideArithmetic.MultiplyMagnitudes(value, Slot(weights, vertex), product); |
| | 2394 | 285 | | Add(product, coordinate.Sign, sum, ref sign); |
| | | 286 | | } |
| | 798 | 287 | | Span<ulong> scaledDenominator = stackalloc ulong[geometry.IsAffine ? Words : 0]; |
| | 798 | 288 | | scoped ReadOnlySpan<ulong> witnessDenominator = denominator; |
| | 798 | 289 | | if (geometry.IsAffine) |
| | | 290 | | { |
| | 60 | 291 | | Span<ulong> scale = stackalloc ulong[5]; |
| | 60 | 292 | | WideArithmetic.GetMagnitude(geometry.RawScale, out scale[4], out scale[3], out scale[2], out scale[1], out s |
| | 60 | 293 | | WideArithmetic.MultiplyMagnitudes(denominator, scale, scaledDenominator); |
| | 60 | 294 | | witnessDenominator = scaledDenominator; |
| | | 295 | | } |
| | 798 | 296 | | bool represented = Fixed64.TryGetSignedRawRatio(sum, witnessDenominator, sign < 0, out Fixed64 result); |
| | | 297 | | System.Diagnostics.Debug.Assert(represented); // A convex combination of representable authored vertices. |
| | 798 | 298 | | return result; |
| | | 299 | | } |
| | | 300 | | |
| | | 301 | | private static void SumWeights(ReadOnlySpan<ulong> weights, Span<ulong> denominator) |
| | | 302 | | { |
| | 487 | 303 | | WideArithmetic.AddEqualMagnitudes(Slot(weights, 0), Slot(weights, 1), denominator); |
| | 487 | 304 | | WideArithmetic.AddMagnitudeInto(Slot(weights, 2), denominator); |
| | 487 | 305 | | } |
| | | 306 | | |
| | | 307 | | private static void SumCoordinates(in TriangleCircularGeometry geometry, ReadOnlySpan<ulong> weights, |
| | | 308 | | int component, Span<ulong> result, out int sign) |
| | | 309 | | { |
| | 661 | 310 | | Span<ulong> coordinate = stackalloc ulong[Words]; |
| | 661 | 311 | | Span<ulong> product = stackalloc ulong[Words]; |
| | 1322 | 312 | | result.Clear(); sign = 0; |
| | 5288 | 313 | | for (int vertex = 0; vertex < 3; vertex++) |
| | | 314 | | { |
| | 1983 | 315 | | Signed576 value = Component(geometry.Vertex(vertex), component); |
| | 1983 | 316 | | Import(value, coordinate); |
| | 1983 | 317 | | WideArithmetic.MultiplyMagnitudes(coordinate, Slot(weights, vertex), product); |
| | 1983 | 318 | | Add(product, value.Sign, result, ref sign); |
| | | 319 | | } |
| | 661 | 320 | | } |
| | | 321 | | } |