| | | 1 | | //======================================================================= |
| | | 2 | | // FixedTriangle.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 System.Runtime.CompilerServices; |
| | | 10 | | using System.Text.Json.Serialization; |
| | | 11 | | using MemoryPack; |
| | | 12 | | |
| | | 13 | | namespace FixedMathSharp.Geometry; |
| | | 14 | | |
| | | 15 | | /// <summary> |
| | | 16 | | /// Represents an ordered triangle in three-dimensional fixed-point space. |
| | | 17 | | /// </summary> |
| | | 18 | | /// <remarks> |
| | | 19 | | /// Triangle queries preserve complete raw-coordinate differences and exact wide |
| | | 20 | | /// predicates until their final public <see cref="Fixed64"/> conversion. |
| | | 21 | | /// </remarks> |
| | | 22 | | [Serializable] |
| | | 23 | | [MemoryPackable] |
| | | 24 | | public partial struct FixedTriangle : IEquatable<FixedTriangle> |
| | | 25 | | { |
| | | 26 | | #region Constants |
| | | 27 | | |
| | | 28 | | /// <summary> |
| | | 29 | | /// The number of vertices in a triangle. |
| | | 30 | | /// </summary> |
| | | 31 | | public const int VertexCount = 3; |
| | | 32 | | |
| | | 33 | | /// <summary> |
| | | 34 | | /// The number of edges in a triangle. |
| | | 35 | | /// </summary> |
| | | 36 | | public const int EdgeCount = 3; |
| | | 37 | | |
| | | 38 | | #endregion |
| | | 39 | | |
| | | 40 | | #region Fields |
| | | 41 | | |
| | | 42 | | /// <summary> |
| | | 43 | | /// The first vertex. |
| | | 44 | | /// </summary> |
| | | 45 | | [JsonInclude] |
| | | 46 | | [MemoryPackOrder(0)] |
| | | 47 | | public Vector3d A; |
| | | 48 | | |
| | | 49 | | /// <summary> |
| | | 50 | | /// The second vertex. |
| | | 51 | | /// </summary> |
| | | 52 | | [JsonInclude] |
| | | 53 | | [MemoryPackOrder(1)] |
| | | 54 | | public Vector3d B; |
| | | 55 | | |
| | | 56 | | /// <summary> |
| | | 57 | | /// The third vertex. |
| | | 58 | | /// </summary> |
| | | 59 | | [JsonInclude] |
| | | 60 | | [MemoryPackOrder(2)] |
| | | 61 | | public Vector3d C; |
| | | 62 | | |
| | | 63 | | #endregion |
| | | 64 | | |
| | | 65 | | #region Constructors |
| | | 66 | | |
| | | 67 | | /// <summary> |
| | | 68 | | /// Initializes a triangle from ordered vertices. |
| | | 69 | | /// </summary> |
| | | 70 | | [JsonConstructor] |
| | | 71 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 72 | | public FixedTriangle(Vector3d a, Vector3d b, Vector3d c) |
| | | 73 | | { |
| | 416 | 74 | | A = a; |
| | 416 | 75 | | B = b; |
| | 416 | 76 | | C = c; |
| | 416 | 77 | | } |
| | | 78 | | |
| | | 79 | | #endregion |
| | | 80 | | |
| | | 81 | | #region Properties |
| | | 82 | | |
| | | 83 | | /// <summary> |
| | | 84 | | /// The unnormalized triangle normal from <c>cross(B - A, C - A)</c>. |
| | | 85 | | /// </summary> |
| | | 86 | | /// <remarks> |
| | | 87 | | /// Each exact cross component is rounded once, half to even, and saturates |
| | | 88 | | /// independently at the public Q32.32 boundary. |
| | | 89 | | /// </remarks> |
| | | 90 | | [JsonIgnore] |
| | | 91 | | [MemoryPackIgnore] |
| | | 92 | | public Vector3d UnnormalizedNormal |
| | | 93 | | { |
| | | 94 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 95 | | get |
| | | 96 | | { |
| | 9 | 97 | | GetExactNormalComponents(out Signed192 x, out Signed192 y, out Signed192 z); |
| | 9 | 98 | | return new Vector3d( |
| | 9 | 99 | | Fixed64.RoundSignedToFixed(x, FixedMath.SHIFT_AMOUNT_I), |
| | 9 | 100 | | Fixed64.RoundSignedToFixed(y, FixedMath.SHIFT_AMOUNT_I), |
| | 9 | 101 | | Fixed64.RoundSignedToFixed(z, FixedMath.SHIFT_AMOUNT_I)); |
| | | 102 | | } |
| | | 103 | | } |
| | | 104 | | |
| | | 105 | | /// <summary> |
| | | 106 | | /// The normalized triangle normal derived from the exact cross product. |
| | | 107 | | /// </summary> |
| | | 108 | | /// <remarks> |
| | | 109 | | /// Components are rounded half to even from the exact squared magnitude. |
| | | 110 | | /// Triangles at or below the inclusive degeneracy threshold return |
| | | 111 | | /// <see cref="Vector3d.Zero"/>. |
| | | 112 | | /// </remarks> |
| | | 113 | | [JsonIgnore] |
| | | 114 | | [MemoryPackIgnore] |
| | | 115 | | public Vector3d Normal |
| | | 116 | | { |
| | | 117 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 118 | | get |
| | | 119 | | { |
| | 21 | 120 | | GetExactNormalComponents(out Signed192 x, out Signed192 y, out Signed192 z); |
| | 21 | 121 | | Signed320 squaredMagnitude = WideGeometry.GetSquaredMagnitude( |
| | 21 | 122 | | x, |
| | 21 | 123 | | y, |
| | 21 | 124 | | z, |
| | 21 | 125 | | out Signed320 xSquare, |
| | 21 | 126 | | out Signed320 ySquare, |
| | 21 | 127 | | out Signed320 zSquare); |
| | 21 | 128 | | if (WideGeometry.IsQ128MagnitudeAtMostEpsilon(squaredMagnitude)) |
| | 5 | 129 | | return Vector3d.Zero; |
| | | 130 | | |
| | 16 | 131 | | Signed192 magnitude = WideArithmetic.GetFloorSquareRoot(squaredMagnitude, out Signed192 remainder); |
| | 16 | 132 | | Signed192 ceilingMagnitude = remainder.IsZero |
| | 16 | 133 | | ? magnitude |
| | 16 | 134 | | : WideArithmetic.AddSigned192(magnitude, Signed192.Signed(1L)); |
| | 16 | 135 | | return new Vector3d( |
| | 16 | 136 | | Fixed64.NormalizeWideComponent(x, xSquare, ceilingMagnitude, squaredMagnitude), |
| | 16 | 137 | | Fixed64.NormalizeWideComponent(y, ySquare, ceilingMagnitude, squaredMagnitude), |
| | 16 | 138 | | Fixed64.NormalizeWideComponent(z, zSquare, ceilingMagnitude, squaredMagnitude)); |
| | | 139 | | } |
| | | 140 | | } |
| | | 141 | | |
| | | 142 | | /// <summary> |
| | | 143 | | /// The non-negative surface area of the triangle. |
| | | 144 | | /// </summary> |
| | | 145 | | /// <remarks> |
| | | 146 | | /// The exact cross-product magnitude is halved and rounded once, half to |
| | | 147 | | /// even. Results beyond the positive Q32.32 range saturate to |
| | | 148 | | /// <see cref="Fixed64.MaxValue"/>. |
| | | 149 | | /// </remarks> |
| | | 150 | | [JsonIgnore] |
| | | 151 | | [MemoryPackIgnore] |
| | | 152 | | public Fixed64 Area |
| | | 153 | | { |
| | | 154 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 155 | | get |
| | | 156 | | { |
| | 16 | 157 | | GetExactNormal(out _, out _, out _, out Signed320 squaredMagnitude); |
| | 16 | 158 | | Signed192 root = WideArithmetic.GetFloorSquareRoot(squaredMagnitude, out Signed192 remainder); |
| | 16 | 159 | | return Fixed64.RoundSquareRootToFixed(root, remainder, FixedMath.SHIFT_AMOUNT_I + 1); |
| | | 160 | | } |
| | | 161 | | } |
| | | 162 | | |
| | | 163 | | /// <summary> |
| | | 164 | | /// The normalized axis-aligned box that contains all vertices. |
| | | 165 | | /// </summary> |
| | | 166 | | [JsonIgnore] |
| | | 167 | | [MemoryPackIgnore] |
| | | 168 | | public FixedBoundBox Bounds |
| | | 169 | | { |
| | | 170 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 2 | 171 | | get => FixedBoundBox.FromMinMax(ComponentMin(ComponentMin(A, B), C), ComponentMax(ComponentMax(A, B), C)); |
| | | 172 | | } |
| | | 173 | | |
| | | 174 | | /// <summary> |
| | | 175 | | /// The arithmetic center of the three vertices, rounded half to even per component. |
| | | 176 | | /// </summary> |
| | | 177 | | [JsonIgnore] |
| | | 178 | | [MemoryPackIgnore] |
| | | 179 | | public Vector3d Centroid |
| | | 180 | | { |
| | | 181 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 82 | 182 | | get => new( |
| | 82 | 183 | | FixedMath.Average(A.X, B.X, C.X), |
| | 82 | 184 | | FixedMath.Average(A.Y, B.Y, C.Y), |
| | 82 | 185 | | FixedMath.Average(A.Z, B.Z, C.Z)); |
| | | 186 | | } |
| | | 187 | | |
| | | 188 | | /// <summary> |
| | | 189 | | /// Returns true when the exact squared normal magnitude is at or below |
| | | 190 | | /// the inclusive <see cref="Fixed64.Epsilon"/> threshold. |
| | | 191 | | /// </summary> |
| | | 192 | | [JsonIgnore] |
| | | 193 | | [MemoryPackIgnore] |
| | | 194 | | public bool IsDegenerate |
| | | 195 | | { |
| | | 196 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 197 | | get |
| | | 198 | | { |
| | 324 | 199 | | GetExactNormal(out _, out _, out _, out Signed320 squaredMagnitude); |
| | 324 | 200 | | return WideGeometry.IsQ128MagnitudeAtMostEpsilon(squaredMagnitude); |
| | | 201 | | } |
| | | 202 | | } |
| | | 203 | | |
| | | 204 | | #endregion |
| | | 205 | | |
| | | 206 | | #region Geometry Access |
| | | 207 | | |
| | | 208 | | /// <summary> |
| | | 209 | | /// Gets a vertex by stable index: 0 = A, 1 = B, 2 = C. |
| | | 210 | | /// </summary> |
| | | 211 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 212 | | public Vector3d GetVertex(int index) => |
| | 5 | 213 | | index switch |
| | 5 | 214 | | { |
| | 1 | 215 | | 0 => A, |
| | 1 | 216 | | 1 => B, |
| | 1 | 217 | | 2 => C, |
| | 2 | 218 | | _ => throw new ArgumentOutOfRangeException(nameof(index), $"Vertex index must be between 0 and {VertexCount |
| | 5 | 219 | | }; |
| | | 220 | | |
| | | 221 | | /// <summary> |
| | | 222 | | /// Gets an edge by stable index: 0 = AB, 1 = BC, 2 = CA. |
| | | 223 | | /// </summary> |
| | | 224 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 225 | | public FixedSegment GetEdge(int index) => |
| | 145 | 226 | | index switch |
| | 145 | 227 | | { |
| | 50 | 228 | | 0 => new FixedSegment(A, B), |
| | 47 | 229 | | 1 => new FixedSegment(B, C), |
| | 46 | 230 | | 2 => new FixedSegment(C, A), |
| | 2 | 231 | | _ => throw new ArgumentOutOfRangeException(nameof(index), $"Edge index must be between 0 and {EdgeCount - 1} |
| | 145 | 232 | | }; |
| | | 233 | | |
| | | 234 | | /// <summary> |
| | | 235 | | /// Gets the point represented by barycentric weights for vertices B and C |
| | | 236 | | /// without saturating intermediate differences. |
| | | 237 | | /// </summary> |
| | | 238 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 239 | | public Vector3d GetPoint(Fixed64 weightB, Fixed64 weightC) => |
| | 111 | 240 | | Vector3d.BarycentricCoordinates(A, B, C, weightB, weightC); |
| | | 241 | | |
| | | 242 | | #endregion |
| | | 243 | | |
| | | 244 | | #region Spatial Queries |
| | | 245 | | |
| | | 246 | | /// <summary> |
| | | 247 | | /// Computes barycentric weights for the point projected onto this triangle's plane. |
| | | 248 | | /// </summary> |
| | | 249 | | /// <returns> |
| | | 250 | | /// True when the exact Gram denominator is outside the inclusive |
| | | 251 | | /// <see cref="Fixed64.Epsilon"/> failure threshold; otherwise false with |
| | | 252 | | /// all three weights set to zero. |
| | | 253 | | /// </returns> |
| | | 254 | | /// <remarks> |
| | | 255 | | /// Successful weights are computed from independent exact numerators, then |
| | | 256 | | /// rounded half to even and saturated independently at the public boundary. |
| | | 257 | | /// The point is projected onto the triangle plane; it need not lie on it. |
| | | 258 | | /// </remarks> |
| | | 259 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 260 | | public bool TryGetProjectedBarycentricWeights(Vector3d point, out Fixed64 weightA, out Fixed64 weightB, out Fixed64 |
| | | 261 | | { |
| | 22 | 262 | | Signed192 abAb = GetDifferenceDot(B, A, B, A); |
| | 22 | 263 | | Signed192 abAc = GetDifferenceDot(B, A, C, A); |
| | 22 | 264 | | Signed192 acAc = GetDifferenceDot(C, A, C, A); |
| | 22 | 265 | | Signed192 apAb = GetDifferenceDot(point, A, B, A); |
| | 22 | 266 | | Signed192 apAc = GetDifferenceDot(point, A, C, A); |
| | 22 | 267 | | Signed320 denominator = WideArithmetic.MultiplySubtract(abAb, acAc, abAc, abAc); |
| | | 268 | | |
| | 22 | 269 | | if (WideGeometry.IsQ128MagnitudeAtMostEpsilon(denominator)) |
| | | 270 | | { |
| | 3 | 271 | | weightA = Fixed64.Zero; |
| | 3 | 272 | | weightB = Fixed64.Zero; |
| | 3 | 273 | | weightC = Fixed64.Zero; |
| | 3 | 274 | | return false; |
| | | 275 | | } |
| | | 276 | | |
| | 19 | 277 | | Signed320 numeratorB = WideArithmetic.MultiplySubtract(acAc, apAb, abAc, apAc); |
| | 19 | 278 | | Signed320 numeratorC = WideArithmetic.MultiplySubtract(abAb, apAc, abAc, apAb); |
| | 19 | 279 | | Signed320 numeratorA = WideArithmetic.SubtractSigned320( |
| | 19 | 280 | | WideArithmetic.SubtractSigned320(denominator, numeratorB), |
| | 19 | 281 | | numeratorC); |
| | 19 | 282 | | weightA = Fixed64.GetSignedRatio(numeratorA, denominator); |
| | 19 | 283 | | weightB = Fixed64.GetSignedRatio(numeratorB, denominator); |
| | 19 | 284 | | weightC = Fixed64.GetSignedRatio(numeratorC, denominator); |
| | 19 | 285 | | return true; |
| | | 286 | | } |
| | | 287 | | |
| | | 288 | | /// <summary> |
| | | 289 | | /// Gets the closest point on this triangle to a point carried by another |
| | | 290 | | /// rigid frame without materializing either absolute world point. |
| | | 291 | | /// </summary> |
| | | 292 | | /// <remarks> |
| | | 293 | | /// The returned anchor remains in the triangle's rigid frame. Voronoi |
| | | 294 | | /// predicates retain the complete relative-frame displacement until the |
| | | 295 | | /// final local barycentric conversion. |
| | | 296 | | /// </remarks> |
| | | 297 | | public FixedPointAnchor GetClosestPointAnchor( |
| | | 298 | | Vector3d triangleOrigin, |
| | | 299 | | FixedQuaternion triangleRotation, |
| | | 300 | | in FixedPointAnchor point) |
| | | 301 | | { |
| | 300 | 302 | | if (!triangleRotation.IsNormalized()) |
| | | 303 | | { |
| | 1 | 304 | | throw new ArgumentException( |
| | 1 | 305 | | "Triangle rotation must be normalized.", |
| | 1 | 306 | | nameof(triangleRotation)); |
| | | 307 | | } |
| | 299 | 308 | | if (!point.Rotation.IsNormalized()) |
| | | 309 | | { |
| | 1 | 310 | | throw new ArgumentException( |
| | 1 | 311 | | "Point rotation must be normalized.", |
| | 1 | 312 | | nameof(point)); |
| | | 313 | | } |
| | | 314 | | |
| | 298 | 315 | | return WideTriangleRelations.GetClosestPointAnchor( |
| | 298 | 316 | | this, |
| | 298 | 317 | | triangleOrigin, |
| | 298 | 318 | | triangleRotation, |
| | 298 | 319 | | point); |
| | | 320 | | } |
| | | 321 | | |
| | | 322 | | /// <summary> |
| | | 323 | | /// Determines whether the supplied point projects within or onto this |
| | | 324 | | /// triangle without requiring the point to lie on its plane. |
| | | 325 | | /// </summary> |
| | | 326 | | /// <remarks> |
| | | 327 | | /// The projected barycentric signs are classified from exact wide |
| | | 328 | | /// numerators. Degenerate triangles have no projected face and return false. |
| | | 329 | | /// </remarks> |
| | | 330 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 331 | | public bool ContainsProjection(Vector3d point) |
| | | 332 | | { |
| | 33 | 333 | | Signed192 abAb = GetDifferenceDot(B, A, B, A); |
| | 33 | 334 | | Signed192 abAc = GetDifferenceDot(B, A, C, A); |
| | 33 | 335 | | Signed192 acAc = GetDifferenceDot(C, A, C, A); |
| | 33 | 336 | | Signed320 denominator = WideArithmetic.MultiplySubtract(abAb, acAc, abAc, abAc); |
| | 33 | 337 | | if (WideGeometry.IsQ128MagnitudeAtMostEpsilon(denominator)) |
| | 1 | 338 | | return false; |
| | | 339 | | |
| | 32 | 340 | | Signed192 apAb = GetDifferenceDot(point, A, B, A); |
| | 32 | 341 | | Signed192 apAc = GetDifferenceDot(point, A, C, A); |
| | 32 | 342 | | Signed320 numeratorB = WideArithmetic.MultiplySubtract(acAc, apAb, abAc, apAc); |
| | 32 | 343 | | if (numeratorB.Sign < 0) |
| | 1 | 344 | | return false; |
| | | 345 | | |
| | 31 | 346 | | Signed320 numeratorC = WideArithmetic.MultiplySubtract(abAb, apAc, abAc, apAb); |
| | 31 | 347 | | if (numeratorC.Sign < 0) |
| | 2 | 348 | | return false; |
| | | 349 | | |
| | 29 | 350 | | return WideArithmetic.SubtractSigned320( |
| | 29 | 351 | | WideArithmetic.SubtractSigned320(denominator, numeratorB), |
| | 29 | 352 | | numeratorC).Sign >= 0; |
| | | 353 | | } |
| | | 354 | | |
| | | 355 | | /// <summary> |
| | | 356 | | /// Determines whether the point is within the inclusive squared-distance |
| | | 357 | | /// epsilon of this triangle. |
| | | 358 | | /// </summary> |
| | | 359 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 360 | | public bool Contains(Vector3d point) |
| | | 361 | | { |
| | 20 | 362 | | return DistanceSquared(point) <= Fixed64.Epsilon; |
| | | 363 | | } |
| | | 364 | | |
| | | 365 | | /// <summary> |
| | | 366 | | /// Finds the closest point on or inside this triangle to the supplied point. |
| | | 367 | | /// </summary> |
| | | 368 | | /// <remarks> |
| | | 369 | | /// Voronoi-region predicates and degenerate edge distances use exact wide |
| | | 370 | | /// intermediates. Degenerate candidates are visited in AB, BC, CA order, |
| | | 371 | | /// and an exact distance tie retains the first candidate. |
| | | 372 | | /// </remarks> |
| | | 373 | | public Vector3d ClosestPoint(Vector3d point) |
| | | 374 | | { |
| | 67 | 375 | | Signed192 abAb = GetDifferenceDot(B, A, B, A); |
| | 67 | 376 | | Signed192 abAc = GetDifferenceDot(B, A, C, A); |
| | 67 | 377 | | Signed192 acAc = GetDifferenceDot(C, A, C, A); |
| | 67 | 378 | | Signed320 denominator = WideArithmetic.MultiplySubtract(abAb, acAc, abAc, abAc); |
| | 67 | 379 | | if (WideGeometry.IsQ128MagnitudeAtMostEpsilon(denominator)) |
| | 16 | 380 | | return ClosestPointOnEdges(point); |
| | | 381 | | |
| | 51 | 382 | | Signed192 d1 = GetDifferenceDot(B, A, point, A); |
| | 51 | 383 | | Signed192 d2 = GetDifferenceDot(C, A, point, A); |
| | 51 | 384 | | if (d1.Sign <= 0 && d2.Sign <= 0) |
| | 3 | 385 | | return A; |
| | | 386 | | |
| | 48 | 387 | | Signed192 d3 = WideArithmetic.SubtractSigned192(d1, abAb); |
| | 48 | 388 | | Signed192 d4 = WideArithmetic.SubtractSigned192(d2, abAc); |
| | 48 | 389 | | if (d3.Sign >= 0 && WideArithmetic.SubtractSigned192(d4, d3).Sign <= 0) |
| | 1 | 390 | | return B; |
| | | 391 | | |
| | 47 | 392 | | Signed320 vc = WideArithmetic.MultiplySubtract(d1, d4, d3, d2); |
| | 47 | 393 | | if (vc.Sign <= 0 && d1.Sign >= 0 && d3.Sign <= 0) |
| | | 394 | | { |
| | 4 | 395 | | _ = Fixed64.TryGetUnitIntervalRatio( |
| | 4 | 396 | | d1, |
| | 4 | 397 | | WideArithmetic.SubtractSigned192(d1, d3), |
| | 4 | 398 | | out Fixed64 parameter); |
| | 4 | 399 | | return Vector3d.Lerp(A, B, parameter); |
| | | 400 | | } |
| | | 401 | | |
| | 43 | 402 | | Signed192 d5 = WideArithmetic.SubtractSigned192(d1, abAc); |
| | 43 | 403 | | Signed192 d6 = WideArithmetic.SubtractSigned192(d2, acAc); |
| | 43 | 404 | | if (d6.Sign >= 0 && WideArithmetic.SubtractSigned192(d5, d6).Sign <= 0) |
| | 1 | 405 | | return C; |
| | | 406 | | |
| | 42 | 407 | | Signed320 vb = WideArithmetic.MultiplySubtract(d5, d2, d1, d6); |
| | 42 | 408 | | if (vb.Sign <= 0 && d2.Sign >= 0 && d6.Sign <= 0) |
| | | 409 | | { |
| | 2 | 410 | | _ = Fixed64.TryGetUnitIntervalRatio( |
| | 2 | 411 | | d2, |
| | 2 | 412 | | WideArithmetic.SubtractSigned192(d2, d6), |
| | 2 | 413 | | out Fixed64 parameter); |
| | 2 | 414 | | return Vector3d.Lerp(A, C, parameter); |
| | | 415 | | } |
| | | 416 | | |
| | 40 | 417 | | Signed320 va = WideArithmetic.MultiplySubtract(d3, d6, d5, d4); |
| | 40 | 418 | | Signed192 d4MinusD3 = WideArithmetic.SubtractSigned192(d4, d3); |
| | 40 | 419 | | Signed192 d5MinusD6 = WideArithmetic.SubtractSigned192(d5, d6); |
| | 40 | 420 | | if (va.Sign <= 0 && d4MinusD3.Sign >= 0 && d5MinusD6.Sign >= 0) |
| | | 421 | | { |
| | 21 | 422 | | _ = Fixed64.TryGetUnitIntervalRatio( |
| | 21 | 423 | | d4MinusD3, |
| | 21 | 424 | | WideArithmetic.AddSigned192(d4MinusD3, d5MinusD6), |
| | 21 | 425 | | out Fixed64 parameter); |
| | 21 | 426 | | return Vector3d.Lerp(B, C, parameter); |
| | | 427 | | } |
| | | 428 | | |
| | 19 | 429 | | _ = Fixed64.TryGetUnitIntervalRatio(vb, denominator, out Fixed64 weightB); |
| | 19 | 430 | | _ = Fixed64.TryGetUnitIntervalRatio(vc, denominator, out Fixed64 weightC); |
| | 19 | 431 | | return GetPoint(weightB, weightC); |
| | | 432 | | } |
| | | 433 | | |
| | | 434 | | /// <summary> |
| | | 435 | | /// Computes the squared distance from the supplied point to this triangle, |
| | | 436 | | /// rounded once and positively saturated at the public boundary. |
| | | 437 | | /// </summary> |
| | | 438 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 439 | | public Fixed64 DistanceSquared(Vector3d point) |
| | | 440 | | { |
| | 28 | 441 | | Vector3d closest = ClosestPoint(point); |
| | 28 | 442 | | Signed192 squaredDistance = GetDifferenceDot(point, closest, point, closest); |
| | 28 | 443 | | return Fixed64.RoundSquaredDistance(squaredDistance); |
| | | 444 | | } |
| | | 445 | | |
| | | 446 | | #endregion |
| | | 447 | | |
| | | 448 | | #region Deconstruction |
| | | 449 | | |
| | | 450 | | /// <summary> |
| | | 451 | | /// Deconstructs the triangle into ordered vertices. |
| | | 452 | | /// </summary> |
| | | 453 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 454 | | public void Deconstruct(out Vector3d a, out Vector3d b, out Vector3d c) |
| | | 455 | | { |
| | 1 | 456 | | a = A; |
| | 1 | 457 | | b = B; |
| | 1 | 458 | | c = C; |
| | 1 | 459 | | } |
| | | 460 | | |
| | | 461 | | #endregion |
| | | 462 | | |
| | | 463 | | #region Operators |
| | | 464 | | |
| | | 465 | | /// <summary> |
| | | 466 | | /// Determines whether two triangles have the same ordered vertices. |
| | | 467 | | /// </summary> |
| | | 468 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 2 | 469 | | public static bool operator ==(FixedTriangle left, FixedTriangle right) => left.Equals(right); |
| | | 470 | | |
| | | 471 | | /// <summary> |
| | | 472 | | /// Determines whether two triangles have different ordered vertices. |
| | | 473 | | /// </summary> |
| | | 474 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 2 | 475 | | public static bool operator !=(FixedTriangle left, FixedTriangle right) => !left.Equals(right); |
| | | 476 | | |
| | | 477 | | #endregion |
| | | 478 | | |
| | | 479 | | #region Equality |
| | | 480 | | |
| | | 481 | | /// <inheritdoc /> |
| | | 482 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 483 | | public bool Equals(FixedTriangle other) |
| | | 484 | | { |
| | 8 | 485 | | return A == other.A && B == other.B && C == other.C; |
| | | 486 | | } |
| | | 487 | | |
| | | 488 | | /// <inheritdoc /> |
| | | 489 | | public override bool Equals(object? obj) |
| | | 490 | | { |
| | 2 | 491 | | return obj is FixedTriangle other && Equals(other); |
| | | 492 | | } |
| | | 493 | | |
| | | 494 | | /// <inheritdoc /> |
| | | 495 | | public override int GetHashCode() |
| | | 496 | | { |
| | | 497 | | unchecked |
| | | 498 | | { |
| | 2 | 499 | | int hash = 17; |
| | 2 | 500 | | hash = (hash * 31) + A.StateHash; |
| | 2 | 501 | | hash = (hash * 31) + B.StateHash; |
| | 2 | 502 | | hash = (hash * 31) + C.StateHash; |
| | 2 | 503 | | return hash; |
| | | 504 | | } |
| | | 505 | | } |
| | | 506 | | |
| | | 507 | | #endregion |
| | | 508 | | |
| | | 509 | | #region Helpers |
| | | 510 | | |
| | | 511 | | private Vector3d ClosestPointOnEdges(Vector3d point) |
| | | 512 | | { |
| | 16 | 513 | | Vector3d best = GetEdge(0).ClosestPoint(point); |
| | 16 | 514 | | TrySetCloserPoint(GetEdge(1), point, ref best); |
| | 16 | 515 | | TrySetCloserPoint(GetEdge(2), point, ref best); |
| | 16 | 516 | | return best; |
| | | 517 | | } |
| | | 518 | | |
| | | 519 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 520 | | private static void TrySetCloserPoint(FixedSegment edge, Vector3d point, ref Vector3d best) |
| | | 521 | | { |
| | 32 | 522 | | Vector3d candidate = edge.ClosestPoint(point); |
| | 32 | 523 | | if (WideGeometry.CompareSquaredDistance3D( |
| | 32 | 524 | | point.X, candidate.X, point.Y, candidate.Y, point.Z, candidate.Z, |
| | 32 | 525 | | point.X, best.X, point.Y, best.Y, point.Z, best.Z) >= 0) |
| | 26 | 526 | | return; |
| | | 527 | | |
| | 6 | 528 | | best = candidate; |
| | 6 | 529 | | } |
| | | 530 | | |
| | | 531 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 532 | | internal void GetExactNormal( |
| | | 533 | | out Signed192 x, |
| | | 534 | | out Signed192 y, |
| | | 535 | | out Signed192 z, |
| | | 536 | | out Signed320 squaredMagnitude) |
| | | 537 | | { |
| | 542 | 538 | | GetExactNormalComponents(out x, out y, out z); |
| | 542 | 539 | | squaredMagnitude = WideGeometry.GetSquaredMagnitude(x, y, z, out _, out _, out _); |
| | 542 | 540 | | } |
| | | 541 | | |
| | | 542 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 543 | | private void GetExactNormalComponents( |
| | | 544 | | out Signed192 x, |
| | | 545 | | out Signed192 y, |
| | | 546 | | out Signed192 z) |
| | | 547 | | { |
| | 572 | 548 | | WideGeometry.GetDifferenceCrossProduct3D( |
| | 572 | 549 | | B.X, A.X, B.Y, A.Y, B.Z, A.Z, |
| | 572 | 550 | | C.X, A.X, C.Y, A.Y, C.Z, A.Z, |
| | 572 | 551 | | out x, |
| | 572 | 552 | | out y, |
| | 572 | 553 | | out z); |
| | 572 | 554 | | } |
| | | 555 | | |
| | | 556 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 557 | | private static Signed192 GetDifferenceDot( |
| | | 558 | | Vector3d leftEnd, |
| | | 559 | | Vector3d leftStart, |
| | | 560 | | Vector3d rightEnd, |
| | | 561 | | Vector3d rightStart) => |
| | 604 | 562 | | WideGeometry.GetDifferenceDotProduct3D( |
| | 604 | 563 | | leftEnd.X, leftStart.X, leftEnd.Y, leftStart.Y, leftEnd.Z, leftStart.Z, |
| | 604 | 564 | | rightEnd.X, rightStart.X, rightEnd.Y, rightStart.Y, rightEnd.Z, rightStart.Z); |
| | | 565 | | |
| | | 566 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 567 | | private static Vector3d ComponentMin(Vector3d a, Vector3d b) |
| | | 568 | | { |
| | 4 | 569 | | return new Vector3d(FixedMath.Min(a.X, b.X), FixedMath.Min(a.Y, b.Y), FixedMath.Min(a.Z, b.Z)); |
| | | 570 | | } |
| | | 571 | | |
| | | 572 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 573 | | private static Vector3d ComponentMax(Vector3d a, Vector3d b) |
| | | 574 | | { |
| | 4 | 575 | | return new Vector3d(FixedMath.Max(a.X, b.X), FixedMath.Max(a.Y, b.Y), FixedMath.Max(a.Z, b.Z)); |
| | | 576 | | } |
| | | 577 | | |
| | | 578 | | #endregion |
| | | 579 | | } |