| | | 1 | | //======================================================================= |
| | | 2 | | // CurvedPrismContactGeometry.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>Exact native-yaw prism geometry in the curved primitive's authored quaternion frame.</summary> |
| | | 14 | | internal readonly ref struct CurvedPrismContactGeometry |
| | | 15 | | { |
| | | 16 | | internal readonly WideRationalBasis3d WorldBasis; |
| | | 17 | | internal readonly WideAxis3 Axis; |
| | | 18 | | internal readonly Signed320 SquaredAxis, Radius; |
| | | 19 | | internal readonly Signed192 HalfFactor, RawScale; |
| | | 20 | | internal readonly WideDirection3d Half; |
| | | 21 | | internal readonly int ValueShift; |
| | | 22 | | internal readonly bool IsCone; |
| | | 23 | | internal readonly Fixed64 PrismHalfThickness, HeightValue, RadiusValue; |
| | | 24 | | private readonly WideConvex2dRelations.RotationFrame2d _yaw; |
| | | 25 | | private readonly ReadOnlySpan<Vector2d> _offsets; |
| | | 26 | | private readonly Signed192 _relativeX, _relativeY, _relativeZ, _thickness; |
| | | 27 | | private readonly Signed192 _coordinateDivisor; |
| | | 28 | | private readonly ReadOnlySpan<WideDirection3d> _vertices; |
| | | 29 | | |
| | 214892 | 30 | | internal int VertexCount => 2 * _offsets.Length; |
| | 7588 | 31 | | internal int EdgeCount => 3 * _offsets.Length; |
| | 3556 | 32 | | internal int PlaneCount => 3 + _offsets.Length; |
| | | 33 | | |
| | | 34 | | internal CurvedPrismContactGeometry(Vector3d center, FixedQuaternion rotation, |
| | | 35 | | Vector3d localAxis, Fixed64 length, Fixed64 radius, Vector3d prismOrigin, |
| | | 36 | | Fixed64 prismYaw, ReadOnlySpan<Vector2d> offsets, Fixed64 halfThickness, bool isCone, |
| | | 37 | | Span<WideDirection3d> vertexStorage) |
| | | 38 | | { |
| | 1034 | 39 | | WorldBasis = new WideRationalBasis3d(rotation); |
| | 3102 | 40 | | Signed192 axisX = Signed192.Raw(localAxis.X), axisY = Signed192.Raw(localAxis.Y), axisZ = Signed192.Raw(localAxi |
| | 1034 | 41 | | Signed192 axisDivisor = WideArithmetic.GetGreatestCommonDivisor( |
| | 1034 | 42 | | WideArithmetic.GetGreatestCommonDivisor(axisX, axisY), axisZ); |
| | 1034 | 43 | | Axis = new WideAxis3(Signed320.ExtendValue(WideArithmetic.DivideExactSigned192(axisX, axisDivisor)), |
| | 1034 | 44 | | Signed320.ExtendValue(WideArithmetic.DivideExactSigned192(axisY, axisDivisor)), |
| | 1034 | 45 | | Signed320.ExtendValue(WideArithmetic.DivideExactSigned192(axisZ, axisDivisor))); |
| | 1034 | 46 | | SquaredAxis = Signed320.NarrowValue(Axis.SquaredLength); |
| | 1034 | 47 | | HalfFactor = Signed192.NarrowProven(WideArithmetic.MultiplySigned192( |
| | 1034 | 48 | | WorldBasis.Denominator, Signed192.Raw(length))); |
| | 1034 | 49 | | HalfFactor = Signed192.NarrowProven(WideArithmetic.MultiplySigned192(HalfFactor, axisDivisor)); |
| | 1034 | 50 | | Half = new WideDirection3d(WideArithmetic.MultiplySigned320(Axis.X, HalfFactor), |
| | 1034 | 51 | | WideArithmetic.MultiplySigned320(Axis.Y, HalfFactor), WideArithmetic.MultiplySigned320(Axis.Z, HalfFactor)); |
| | 1034 | 52 | | RawScale = Signed192.NarrowProven(WideArithmetic.MultiplySigned192( |
| | 1034 | 53 | | WorldBasis.Denominator, Signed192.Raw(Fixed64.Two))); |
| | 1034 | 54 | | Radius = WideArithmetic.MultiplySigned192(RawScale, Signed192.Raw(radius)); |
| | 4136 | 55 | | IsCone = isCone; PrismHalfThickness = halfThickness; HeightValue = length; RadiusValue = radius; |
| | 1034 | 56 | | _yaw = new WideConvex2dRelations.RotationFrame2d(prismYaw); |
| | 1034 | 57 | | _offsets = offsets; |
| | 1034 | 58 | | _relativeX = ScaleRaw(WideArithmetic.Difference(prismOrigin.X, center.X)); |
| | 1034 | 59 | | _relativeY = ScaleRaw(WideArithmetic.Difference(prismOrigin.Y, center.Y)); |
| | 1034 | 60 | | _relativeZ = ScaleRaw(WideArithmetic.Difference(prismOrigin.Z, center.Z)); |
| | 1034 | 61 | | _thickness = ScaleRaw(Signed192.Raw(halfThickness)); |
| | 1034 | 62 | | _coordinateDivisor = Signed192.Signed(1); |
| | 1034 | 63 | | _vertices = default; |
| | 1034 | 64 | | ValueShift = 0; |
| | | 65 | | |
| | | 66 | | // Native yaw vertices V have denominator Q. The exact quaternion |
| | | 67 | | // transpose gives P=2*B^T*V, S=2*D*Q, H=(D*Lraw)*A, R=S*Rraw. |
| | | 68 | | // Primitive Axis=A/g and T=D*Lraw*g preserve the authored half-axis; |
| | | 69 | | // no alignment quaternion or normalization changes its actual norm. |
| | | 70 | | // D<2^65 and the original |A|<2^33 |
| | | 71 | | // give q<2^65, |P|<2^165, |H|,R<2^163 and S<2^99. |
| | | 72 | | // These bounds include full-domain origin differences and native yaw |
| | | 73 | | // offsets. T<2^161 and all coordinate scalars fit Signed192. Divide |
| | | 74 | | // S,T,R and every P by one positive content, retaining all ratios and |
| | | 75 | | // H=T*Axis exactly. T²*q is unchanged by primitive-axis reduction. |
| | 1034 | 76 | | Signed192 divisor = WideArithmetic.GetGreatestCommonDivisor(RawScale, HalfFactor); |
| | 1034 | 77 | | divisor = WideArithmetic.GetGreatestCommonDivisor(divisor, Signed192.NarrowValue(Radius)); |
| | 1034 | 78 | | bool cache = VertexCount <= vertexStorage.Length; |
| | 19168 | 79 | | for (int index = 0; index < VertexCount; index++) |
| | | 80 | | { |
| | 8550 | 81 | | WideDirection3d vertex = GetNativeVertex(index); |
| | 16828 | 82 | | if (cache) vertexStorage[index] = vertex; |
| | 8550 | 83 | | divisor = WideArithmetic.GetGreatestCommonDivisor(divisor, Signed192.NarrowValue(vertex.X)); |
| | 8550 | 84 | | divisor = WideArithmetic.GetGreatestCommonDivisor(divisor, Signed192.NarrowValue(vertex.Y)); |
| | 8550 | 85 | | divisor = WideArithmetic.GetGreatestCommonDivisor(divisor, Signed192.NarrowValue(vertex.Z)); |
| | | 86 | | } |
| | 1034 | 87 | | _coordinateDivisor = divisor; |
| | 1034 | 88 | | RawScale = WideArithmetic.DivideExactSigned192(RawScale, divisor); |
| | 1034 | 89 | | HalfFactor = WideArithmetic.DivideExactSigned192(HalfFactor, divisor); |
| | 1034 | 90 | | Radius = Signed320.ExtendValue(WideArithmetic.DivideExactSigned192(Signed192.NarrowValue(Radius), divisor)); |
| | 1034 | 91 | | Half = new WideDirection3d(WideArithmetic.MultiplySigned320(Axis.X, HalfFactor), |
| | 1034 | 92 | | WideArithmetic.MultiplySigned320(Axis.Y, HalfFactor), WideArithmetic.MultiplySigned320(Axis.Z, HalfFactor)); |
| | 1034 | 93 | | Span<ulong> magnitude = stackalloc ulong[9]; |
| | 1034 | 94 | | int bits = Math.Max(Bits(Signed576.ExtendValue(Radius), magnitude), |
| | 1034 | 95 | | Math.Max(Bits(Half.X, magnitude), Math.Max(Bits(Half.Y, magnitude), Bits(Half.Z, magnitude)))); |
| | 19168 | 96 | | for (int index = 0; index < VertexCount; index++) |
| | | 97 | | { |
| | 8550 | 98 | | WideDirection3d vertex = ReduceVertex(cache ? vertexStorage[index] : GetNativeVertex(index), divisor); |
| | 16828 | 99 | | if (cache) vertexStorage[index] = vertex; |
| | 8550 | 100 | | bits = Math.Max(bits, Math.Max(Bits(vertex.X, magnitude), |
| | 8550 | 101 | | Math.Max(Bits(vertex.Y, magnitude), Bits(vertex.Z, magnitude)))); |
| | | 102 | | } |
| | | 103 | | // Borrow only a complete query-owned cache. Larger footprints stream |
| | | 104 | | // the same reduced vertices and never allocate or change feature order. |
| | 2058 | 105 | | if (cache) _vertices = vertexStorage[..VertexCount]; |
| | | 106 | | // Every projected support residual is shorter than the sum of its |
| | | 107 | | // vertex, half-axis and radial terms. Four bits cover that sum and |
| | | 108 | | // three-coordinate length before squaring onto the retained value axis. |
| | 1034 | 109 | | ValueShift = 2 * (bits + 4); |
| | 1034 | 110 | | } |
| | | 111 | | |
| | | 112 | | /// <summary>Returns authored vertex 2*i at the bottom and 2*i+1 at the top.</summary> |
| | 230131 | 113 | | internal WideDirection3d Vertex(int index) => !_vertices.IsEmpty ? _vertices[index] |
| | 230131 | 114 | | : ReduceVertex(GetNativeVertex(index), _coordinateDivisor); |
| | | 115 | | |
| | 73 | 116 | | internal Signed192 NativeRawScale => Signed192.NarrowProven(WideArithmetic.MultiplySigned192(RawScale, _coordinateDi |
| | | 117 | | |
| | | 118 | | // Boundary cuts retain their original source coordinates; do not assume |
| | | 119 | | // the content of the original vertices also divides a new height-band point. |
| | | 120 | | internal WideDirection3d GetNativeSourcePoint(Vector3d point) => |
| | 146 | 121 | | GetNativeSourcePoint(new Vector2d(point.X, point.Z), ScaleRaw(Signed192.Raw(point.Y))); |
| | | 122 | | |
| | | 123 | | private WideDirection3d GetNativeVertex(int index) => |
| | 20972 | 124 | | GetNativeSourcePoint(_offsets[index / 2], (index & 1) == 0 ? WideArithmetic.Negate(_thickness) : _thickness); |
| | | 125 | | |
| | | 126 | | private WideDirection3d GetNativeSourcePoint(Vector2d offset, Signed192 y) |
| | | 127 | | { |
| | 21118 | 128 | | WideConvex2dRelations.GetRotatedOffset(_yaw, offset, out Signed192 x, out Signed192 z); |
| | 21118 | 129 | | var native = new WideAxis3(Signed320.ExtendValue(WideArithmetic.AddSigned192(_relativeX, x)), |
| | 21118 | 130 | | Signed320.ExtendValue(WideArithmetic.AddSigned192(_relativeY, y)), |
| | 21118 | 131 | | Signed320.ExtendValue(WideArithmetic.AddSigned192(_relativeZ, z))); |
| | 21118 | 132 | | WideRigidProjection.GetLocalAxisProjections(native, WorldBasis, out Signed576 px, out Signed576 py, out Signed57 |
| | 21118 | 133 | | return new WideDirection3d(WideArithmetic.AddSigned576(px, px), |
| | 21118 | 134 | | WideArithmetic.AddSigned576(py, py), WideArithmetic.AddSigned576(pz, pz)); |
| | | 135 | | } |
| | | 136 | | |
| | 108 | 137 | | internal Vector2d FootprintVertex(int index) => _offsets[index]; |
| | | 138 | | |
| | | 139 | | /// <summary>Returns the bottom, top and vertical real edges of each footprint vertex.</summary> |
| | | 140 | | internal void Edge(int index, out int start, out int end) |
| | | 141 | | { |
| | 34450 | 142 | | int vertex = index / 3, kind = index % 3; |
| | 17225 | 143 | | int next = vertex + 1 == _offsets.Length ? 0 : vertex + 1; |
| | 17225 | 144 | | start = 2 * vertex + (kind == 1 ? 1 : 0); |
| | 17225 | 145 | | end = kind == 2 ? start + 1 : 2 * next + kind; |
| | 17225 | 146 | | } |
| | | 147 | | |
| | | 148 | | /// <summary>Returns exact native coordinate and footprint-edge plane normals.</summary> |
| | | 149 | | internal WideDirection3d Plane(int index) |
| | | 150 | | { |
| | 64963 | 151 | | WideDirection3d up = new(Signed576.ExtendValue(WorldBasis.Xy), |
| | 64963 | 152 | | Signed576.ExtendValue(WorldBasis.Yy), Signed576.ExtendValue(WorldBasis.Zy)); |
| | 65711 | 153 | | if (index == 0) return new WideDirection3d(Signed576.ExtendValue(WorldBasis.Xx), |
| | 748 | 154 | | Signed576.ExtendValue(WorldBasis.Yx), Signed576.ExtendValue(WorldBasis.Zx)); |
| | 121499 | 155 | | if (index == 1) return up; |
| | 7284 | 156 | | if (index == 2) return new WideDirection3d(Signed576.ExtendValue(WorldBasis.Xz), |
| | 353 | 157 | | Signed576.ExtendValue(WorldBasis.Yz), Signed576.ExtendValue(WorldBasis.Zz)); |
| | 6578 | 158 | | Edge(3 * (index - 3), out int start, out int end); |
| | 13156 | 159 | | WideDirection3d a = Vertex(start), b = Vertex(end); |
| | 6578 | 160 | | WideDirection3d edge = new(WideArithmetic.SubtractSigned576(b.X, a.X), |
| | 6578 | 161 | | WideArithmetic.SubtractSigned576(b.Y, a.Y), WideArithmetic.SubtractSigned576(b.Z, a.Z)); |
| | 6578 | 162 | | return WideDirection3d.Cross(edge, up); |
| | | 163 | | } |
| | | 164 | | |
| | | 165 | | /// <summary>Returns the two inward incident face normals of a proper real-edge support cone.</summary> |
| | | 166 | | internal bool TryGetEdgeNormalCone(int edge, out WideDirection3d first, out WideDirection3d last) |
| | | 167 | | { |
| | 2372 | 168 | | int vertex = edge / 3, kind = edge % 3; |
| | 1186 | 169 | | int previous = vertex == 0 ? _offsets.Length - 1 : vertex - 1; |
| | 1186 | 170 | | first = kind == 2 ? GetSidePlane(previous) : Plane(1); |
| | 1521 | 171 | | if (kind == 1) first = -first; |
| | 1186 | 172 | | last = GetSidePlane(vertex); |
| | | 173 | | // The cap normal already minimizes at its bottom/top edge. Vertical |
| | | 174 | | // edges require both incident wall orientations to be certified. |
| | 1186 | 175 | | if ((kind == 2 && !TryOrientSidePlane(previous, ref first)) || !TryOrientSidePlane(vertex, ref last) |
| | 1186 | 176 | | || WideDirection3d.AreParallel(first, last)) |
| | 59 | 177 | | return false; |
| | | 178 | | // Each endpoint supports the complete prism, so its positive span |
| | | 179 | | // does too. Primitive side normals are no larger than native |
| | | 180 | | // B^T*(-dz,0,dx), below 165 bits; their difference stays below 166. |
| | 1127 | 181 | | Span<Signed576> components = stackalloc Signed576[3]; |
| | 3381 | 182 | | components[0] = first.X; components[1] = first.Y; components[2] = first.Z; |
| | 1127 | 183 | | WideArithmetic.ReduceCommonScale(components); |
| | 1127 | 184 | | first = new WideDirection3d(components[0], components[1], components[2]); |
| | 3381 | 185 | | components[0] = last.X; components[1] = last.Y; components[2] = last.Z; |
| | 1127 | 186 | | WideArithmetic.ReduceCommonScale(components); |
| | 1127 | 187 | | last = new WideDirection3d(components[0], components[1], components[2]); |
| | 1127 | 188 | | return true; |
| | | 189 | | } |
| | | 190 | | |
| | | 191 | | /// <summary>Whether a side plane duplicates an earlier native X/Z coordinate plane.</summary> |
| | | 192 | | internal bool IsCoordinateSidePlane(int plane) |
| | | 193 | | { |
| | 1022 | 194 | | int vertex = plane - 3; |
| | 1022 | 195 | | int next = vertex + 1 == _offsets.Length ? 0 : vertex + 1; |
| | | 196 | | // Full raw differences need65 bits. Test the exact authored yaw map, |
| | | 197 | | // before quaternion projection or any public Fixed64 narrowing. |
| | 1022 | 198 | | Signed192 dx = WideArithmetic.Difference(_offsets[next].X, _offsets[vertex].X); |
| | 1022 | 199 | | Signed192 dz = WideArithmetic.Difference(_offsets[next].Y, _offsets[vertex].Y); |
| | 1022 | 200 | | WideConvex2dRelations.GetRotatedOffset(_yaw, dx, dz, out Signed192 yawX, out Signed192 yawZ); |
| | 1022 | 201 | | return yawX.IsZero || yawZ.IsZero; |
| | | 202 | | } |
| | | 203 | | |
| | | 204 | | private WideDirection3d GetSidePlane(int vertex) |
| | | 205 | | { |
| | 1710 | 206 | | int next = vertex + 1 == _offsets.Length ? 0 : vertex + 1; |
| | | 207 | | // Full raw differences need 65 bits; Vector2d subtraction can saturate. |
| | 1710 | 208 | | Signed192 dx = WideArithmetic.Difference(_offsets[next].X, _offsets[vertex].X); |
| | 1710 | 209 | | Signed192 dz = WideArithmetic.Difference(_offsets[next].Y, _offsets[vertex].Y); |
| | 1710 | 210 | | WideConvex2dRelations.GetRotatedOffset(_yaw, dx, dz, out Signed192 yawX, out Signed192 yawZ); |
| | 1710 | 211 | | var native = new WideAxis3(Signed320.ExtendValue(WideArithmetic.Negate(yawZ)), |
| | 1710 | 212 | | default, Signed320.ExtendValue(yawX)); |
| | | 213 | | // The old Cross(localEdge,localUp) is this vector times the positive |
| | | 214 | | // 2*WorldBasis.Denominator/_coordinateDivisor. The existing endpoint |
| | | 215 | | // reduction returns exactly the same primitive integer vector. Yaw |
| | | 216 | | // differences are below 99 bits and transformed components below 166. |
| | 1710 | 217 | | WideRigidProjection.GetLocalAxisProjections(native, WorldBasis, |
| | 1710 | 218 | | out Signed576 x, out Signed576 y, out Signed576 z); |
| | 1710 | 219 | | return new WideDirection3d(x, y, z); |
| | | 220 | | } |
| | | 221 | | |
| | | 222 | | private bool TryOrientSidePlane(int side, ref WideDirection3d normal) |
| | | 223 | | { |
| | 1711 | 224 | | if (normal.IsZero) return false; |
| | 1633 | 225 | | int next = side + 1 == _offsets.Length ? 0 : side + 1; |
| | 3266 | 226 | | Vector2d a = _offsets[side], b = _offsets[next]; |
| | 3266 | 227 | | Signed192 dx = WideArithmetic.Difference(b.X, a.X), dz = WideArithmetic.Difference(b.Y, a.Y); |
| | 3266 | 228 | | int minimumSign = 0, maximumSign = 0; |
| | | 229 | | // Both transforms multiply side-plane dot differences by a positive |
| | | 230 | | // squared norm. Native determinants preserve every support equality, |
| | | 231 | | // including reversed, repeated and two-sided footprint boundaries. |
| | | 232 | | // Full raw differences are below65 bits; determinants below131 fit320. |
| | 16342 | 233 | | foreach (Vector2d point in _offsets) |
| | | 234 | | { |
| | 6538 | 235 | | int sign = WideArithmetic.MultiplySubtract(dx, WideArithmetic.Difference(point.Y, a.Y), |
| | 6538 | 236 | | dz, WideArithmetic.Difference(point.X, a.X)).Sign; |
| | 6538 | 237 | | minimumSign = Math.Min(minimumSign, sign); |
| | 6538 | 238 | | maximumSign = Math.Max(maximumSign, sign); |
| | | 239 | | } |
| | 1633 | 240 | | int orientation = minimumSign + maximumSign; |
| | 1651 | 241 | | if (orientation == 0) return false; |
| | 1839 | 242 | | if (orientation < 0) normal = -normal; |
| | 1615 | 243 | | return true; |
| | | 244 | | } |
| | | 245 | | |
| | | 246 | | /// <summary>Returns the exact lower support-plane constant in common coordinates.</summary> |
| | 15192 | 247 | | internal Signed832 ProjectionMinimum(in WideDirection3d normal) => ProjectionMinimum(normal, out _); |
| | | 248 | | |
| | | 249 | | private Signed832 ProjectionMinimum(in WideDirection3d normal, out int vertex) |
| | | 250 | | { |
| | | 251 | | // Rational fan directions are below 504 bits and vertices below 165, |
| | | 252 | | // so dot products and their differences remain below 672 bits. |
| | | 253 | | // Every top-minus-bottom is the same positive multiple of native Up. |
| | | 254 | | // Choose that supporting level once; bottom preserves first-index ties. |
| | 22062 | 255 | | vertex = WideDirection3d.Dot(normal, Plane(1)).Sign < 0 ? 1 : 0; |
| | 22062 | 256 | | Signed832 minimum = WideDirection3d.Dot(normal, Vertex(vertex)); |
| | 183518 | 257 | | for (int index = vertex + 2; index < VertexCount; index += 2) |
| | | 258 | | { |
| | 69697 | 259 | | Signed832 current = WideDirection3d.Dot(normal, Vertex(index)); |
| | 69697 | 260 | | if (WideArithmetic.SubtractSigned832(current, minimum).Sign >= 0) continue; |
| | 17066 | 261 | | minimum = current; vertex = index; |
| | | 262 | | } |
| | 22062 | 263 | | return minimum; |
| | | 264 | | } |
| | | 265 | | |
| | | 266 | | /// <summary> |
| | | 267 | | /// Retains the first minimum projection under a seven-slot local normal |
| | | 268 | | /// (rational XYZ, radical XYZ, radicand). Result uses forty-word fields and |
| | | 269 | | /// must be disjoint from normal storage. The finite fan's rational fields |
| | | 270 | | /// are below 768 bits, radical fields below 512 and radicand below 832; |
| | | 271 | | /// vertex dots and their exact quadratic sign products fit forty words. |
| | | 272 | | /// </summary> |
| | | 273 | | internal void GetMinimum(ReadOnlySpan<ulong> normal, ReadOnlySpan<int> normalSigns, |
| | | 274 | | ContactQuadratic result, out int vertexIndex) |
| | | 275 | | { |
| | 2824 | 276 | | Span<ulong> work = stackalloc ulong[4 * Words]; |
| | 2824 | 277 | | Span<int> signs = stackalloc int[4]; |
| | 2824 | 278 | | ContactQuadratic current = ContactQuadratic.At(work, signs, 0); |
| | 2824 | 279 | | ContactQuadratic difference = ContactQuadratic.At(work, signs, 1); |
| | 2824 | 280 | | DotNormal(Plane(1), normal, normalSigns, current); |
| | 2824 | 281 | | vertexIndex = current.Sign(Slot(normal, 6)) < 0 ? 1 : 0; |
| | 2824 | 282 | | DotNormal(Vertex(vertexIndex), normal, normalSigns, result); |
| | 25584 | 283 | | for (int index = vertexIndex + 2; index < VertexCount; index += 2) |
| | | 284 | | { |
| | 9968 | 285 | | DotNormal(Vertex(index), normal, normalSigns, current); |
| | 19936 | 286 | | current.CopyTo(difference); difference.Add(result, -1); |
| | 9968 | 287 | | if (difference.Sign(Slot(normal, 6)) >= 0) continue; |
| | 3774 | 288 | | current.CopyTo(result); vertexIndex = index; |
| | | 289 | | } |
| | 2824 | 290 | | } |
| | | 291 | | |
| | | 292 | | /// <summary> |
| | | 293 | | /// Encodes the complete support gap for a nonzero rational local direction, retaining |
| | | 294 | | /// that exact direction locally. Input and candidate storage are disjoint. |
| | | 295 | | /// Feature is -1 for bottom/base, +1 for top/apex, zero for a side/generator |
| | | 296 | | /// tie. A required vertex admits only its supporting normal directions. |
| | | 297 | | /// Directions fit Signed576; all support-gap products fit forty words. |
| | | 298 | | /// </summary> |
| | | 299 | | internal bool TryBuildCandidate(in WideDirection3d normal, Span<ulong> direction, Span<int> directionSigns, |
| | | 300 | | Span<ulong> values, Span<int> signs, int requiredVertex, out int gapSign, out int vertex, out int feature) |
| | | 301 | | { |
| | 97548 | 302 | | gapSign = 0; feature = 0; vertex = requiredVertex; |
| | | 303 | | Signed832 minimum; |
| | 32516 | 304 | | if (requiredVertex >= 0) |
| | | 305 | | { |
| | | 306 | | // A required edge endpoint cannot support from the wrong extrusion level; zero admits both ties. |
| | 25646 | 307 | | int upSign = WideDirection3d.Dot(normal, Plane(1)).Sign; |
| | 32715 | 308 | | if ((requiredVertex & 1) == 0 ? upSign < 0 : upSign > 0) return false; |
| | 18577 | 309 | | int level = upSign < 0 ? 1 : 0; |
| | | 310 | | // At zero Up projection the levels tie: retain the original even |
| | | 311 | | // level and first footprint index, not the authored required level. |
| | 18577 | 312 | | int reference = (requiredVertex & ~1) + level; |
| | 18577 | 313 | | minimum = WideDirection3d.Dot(normal, Vertex(reference)); |
| | 18577 | 314 | | vertex = reference; |
| | 144934 | 315 | | for (int index = level; index < VertexCount; index += 2) |
| | | 316 | | { |
| | 62095 | 317 | | Signed832 current = index == reference ? minimum : WideDirection3d.Dot(normal, Vertex(index)); |
| | 62095 | 318 | | int comparison = WideArithmetic.SubtractSigned832(current, minimum).Sign; |
| | 70300 | 319 | | if (comparison < 0) return false; |
| | 59901 | 320 | | if (comparison == 0 && index < vertex) vertex = index; |
| | | 321 | | } |
| | | 322 | | } |
| | 6870 | 323 | | else minimum = ProjectionMinimum(normal, out vertex); |
| | 17242 | 324 | | WriteDirection(normal, direction, directionSigns); |
| | 17242 | 325 | | gapSign = BuildPlaneCandidate(direction, directionSigns, normal, minimum, values, signs, out feature); |
| | 17242 | 326 | | return true; |
| | | 327 | | } |
| | | 328 | | |
| | | 329 | | |
| | | 330 | | // A retained native face uses its own known plane constant. In particular, |
| | | 331 | | // its opposite gap tests plane intersection, not the prism's opposite face. |
| | | 332 | | internal int BuildPlaneCandidate(ReadOnlySpan<ulong> direction, ReadOnlySpan<int> directionSigns, |
| | | 333 | | in WideDirection3d normal, Signed832 minimum, Span<ulong> values, Span<int> signs, out int feature) |
| | | 334 | | { |
| | 17436 | 335 | | int words = values.Length / signs.Length; |
| | 34872 | 336 | | values.Clear(); signs.Clear(); |
| | 34872 | 337 | | direction.CopyTo(values); directionSigns.CopyTo(signs); |
| | 17436 | 338 | | Span<ulong> work = stackalloc ulong[14 * words]; |
| | 52308 | 339 | | Span<ulong> minimumMagnitude = Slot(work, 0, words), half = Slot(work, 1, words), metric = Slot(work, 2, words); |
| | 69744 | 340 | | Span<ulong> axial = Slot(work, 3, words), q = Slot(work, 4, words), radial = Slot(work, 5, words), radiusSquared |
| | 69744 | 341 | | Span<ulong> p = Slot(work, 7, words), root = Slot(work, 8, words), denominator = Slot(work, 9, words), temporary |
| | 52308 | 342 | | Span<ulong> product = Slot(work, 11, words), scale = Slot(work, 12, words), rational = Slot(work, 13, words); |
| | 17436 | 343 | | Import(minimum, minimumMagnitude); |
| | 17436 | 344 | | Signed832 axisProjection = WideDirection3d.Dot(normal, Axis); |
| | | 345 | | // Half=HalfFactor*Axis remains exact after both common-scale reductions. |
| | 17436 | 346 | | Signed832 halfProjection = WideArithmetic.MultiplySigned832(axisProjection, HalfFactor); |
| | 17436 | 347 | | Import(halfProjection, half); |
| | 17436 | 348 | | int halfSign = halfProjection.Sign; |
| | 17436 | 349 | | SumSquares(direction, metric); |
| | 17436 | 350 | | Import(axisProjection, axial); |
| | 17436 | 351 | | Import(SquaredAxis, q); |
| | 17436 | 352 | | WideArithmetic.MultiplyMagnitudes(q, metric, temporary); |
| | 17436 | 353 | | WideArithmetic.MultiplyMagnitudes(axial, axial, product); |
| | 17436 | 354 | | WideArithmetic.SubtractEqualMagnitudes(temporary, product, radial); |
| | 17436 | 355 | | Import(Radius, temporary); |
| | 17436 | 356 | | WideArithmetic.MultiplyMagnitudes(temporary, temporary, radiusSquared); |
| | 17436 | 357 | | bool apex = false; |
| | 17436 | 358 | | feature = halfSign; |
| | 17436 | 359 | | if (IsCone) |
| | | 360 | | { |
| | 6301 | 361 | | int comparison = -1; |
| | 6301 | 362 | | if (halfSign >= 0) |
| | | 363 | | { |
| | 4119 | 364 | | WideArithmetic.MultiplyMagnitudes(half, half, temporary); |
| | 4119 | 365 | | WideArithmetic.MultiplyMagnitudes(q, temporary, product); |
| | 8238 | 366 | | WideArithmetic.AddMagnitudeInto(product, product); WideArithmetic.AddMagnitudeInto(product, product); |
| | 4119 | 367 | | WideArithmetic.MultiplyMagnitudes(radiusSquared, radial, temporary); |
| | 4119 | 368 | | comparison = WideArithmetic.CompareMagnitudeEqualLength(product, temporary); |
| | | 369 | | } |
| | 6301 | 370 | | apex = comparison >= 0; |
| | 6301 | 371 | | feature = comparison == 0 ? 0 : apex ? 1 : -1; |
| | | 372 | | } |
| | 17436 | 373 | | half.CopyTo(rational); |
| | 17436 | 374 | | int rationalSign = IsCone ? (apex ? halfSign : -halfSign) : IsZero(half) ? 0 : 1; |
| | 17436 | 375 | | Add(minimumMagnitude, -minimum.Sign, rational, ref rationalSign); |
| | 17436 | 376 | | Import(Signed576.ExtendValue(RawScale), scale); |
| | 17436 | 377 | | WideArithmetic.MultiplyMagnitudes(scale, scale, temporary); |
| | 17436 | 378 | | WideArithmetic.MultiplyMagnitudes(temporary, metric, denominator); |
| | 17436 | 379 | | if (IsCone && apex) |
| | | 380 | | { |
| | 3378 | 381 | | rational.CopyTo(p); root.Clear(); |
| | | 382 | | } |
| | | 383 | | else |
| | | 384 | | { |
| | | 385 | | // g=(q*T+sqrt(q*R²*(q*n²-(Axis.n)²)))/(q*S*|n|). |
| | | 386 | | // Retain the positive q scale exactly; no axis norm is rounded. |
| | 15747 | 387 | | WideArithmetic.MultiplyMagnitudes(q, rational, p); |
| | 15747 | 388 | | WideArithmetic.MultiplyMagnitudes(radiusSquared, radial, temporary); |
| | 15747 | 389 | | WideArithmetic.MultiplyMagnitudes(q, temporary, root); |
| | 15747 | 390 | | WideArithmetic.MultiplyMagnitudes(q, q, temporary); |
| | 31494 | 391 | | WideArithmetic.MultiplyMagnitudes(denominator, temporary, product); product.CopyTo(denominator); |
| | | 392 | | } |
| | 17436 | 393 | | int gapSign = BuildRadialCandidate(p, rationalSign, root, denominator, values, signs); |
| | 17436 | 394 | | return gapSign; |
| | | 395 | | } |
| | | 396 | | |
| | | 397 | | private static void DotNormal(in WideDirection3d point, ReadOnlySpan<ulong> normal, |
| | | 398 | | ReadOnlySpan<int> normalSigns, ContactQuadratic result) |
| | | 399 | | { |
| | 15616 | 400 | | Dot(point, normal[..(3 * Words)], normalSigns[..3], result.Rational, out int rationalSign); |
| | 15616 | 401 | | Dot(point, normal.Slice(3 * Words, 3 * Words), normalSigns.Slice(3, 3), result.Radical, out int radicalSign); |
| | 31232 | 402 | | result.Signs[0] = rationalSign; result.Signs[1] = radicalSign; |
| | 15616 | 403 | | } |
| | | 404 | | |
| | | 405 | | private static Signed192 ScaleRaw(Signed192 value) => |
| | 4282 | 406 | | Signed192.NarrowProven(WideArithmetic.MultiplySigned192(value, Signed192.One)); |
| | | 407 | | |
| | | 408 | | internal static Signed576 ReadDirectionComponent(ReadOnlySpan<ulong> magnitude, int sign) |
| | | 409 | | { |
| | | 410 | | // Rational fan components fit 504 bits; the remaining padded words |
| | | 411 | | // contain no information. Quadratic generator normals use GetMinimum. |
| | 28108 | 412 | | var value = new Signed576(magnitude[8], magnitude[7], magnitude[6], magnitude[5], magnitude[4], |
| | 28108 | 413 | | magnitude[3], magnitude[2], magnitude[1], magnitude[0]); |
| | 28108 | 414 | | return sign < 0 ? WideArithmetic.Negate(value) : value; |
| | | 415 | | } |
| | | 416 | | |
| | 20700 | 417 | | private static WideDirection3d ReduceVertex(in WideDirection3d vertex, Signed192 divisor) => new( |
| | 20700 | 418 | | Signed576.ExtendValue(WideArithmetic.DivideExactSigned192(Signed192.NarrowValue(vertex.X), divisor)), |
| | 20700 | 419 | | Signed576.ExtendValue(WideArithmetic.DivideExactSigned192(Signed192.NarrowValue(vertex.Y), divisor)), |
| | 20700 | 420 | | Signed576.ExtendValue(WideArithmetic.DivideExactSigned192(Signed192.NarrowValue(vertex.Z), divisor))); |
| | | 421 | | |
| | | 422 | | private static int Bits(Signed576 value, Span<ulong> magnitude) |
| | | 423 | | { |
| | 29786 | 424 | | WideArithmetic.GetMagnitude(value, magnitude); |
| | 29786 | 425 | | return WideArithmetic.GetMagnitudeBitLength(magnitude); |
| | | 426 | | } |
| | | 427 | | } |