< Summary

Information
Class: Gravitas.CollisionHandling.ContactNormalImpulseResult2D
Assembly: Gravitas
File(s): /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/2D/ContactNormalImpulse2D.cs
Line coverage
100%
Covered lines: 21
Uncovered lines: 0
Coverable lines: 21
Total lines: 717
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.ctor(...)100%11100%

File(s)

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/2D/ContactNormalImpulse2D.cs

#LineLine coverage
 1//=======================================================================
 2// ContactNormalImpulse2D.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026–present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using FixedMathSharp;
 9using FixedMathSharp.Geometry;
 10using System.Runtime.CompilerServices;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14/// <summary>
 15/// Side-effect-free 2D contact-normal impulse result for two participants.
 16/// </summary>
 17internal readonly struct ContactNormalImpulseResult2D
 18{
 19    public ContactNormalImpulseResult2D(
 20        Fixed64 normalVelocity,
 21        Fixed64 impulseScalar,
 22        Vector2d linearVelocityDeltaA,
 23        Fixed64 angularVelocityDeltaA,
 24        Vector2d linearVelocityDeltaB,
 25        Fixed64 angularVelocityDeltaB)
 97426        : this(
 97427            normalVelocity,
 97428            impulseScalar,
 97429            impulseScalar,
 97430            linearVelocityDeltaA,
 97431            angularVelocityDeltaA,
 97432            linearVelocityDeltaB,
 97433            angularVelocityDeltaB)
 34    {
 97435    }
 36
 37    public ContactNormalImpulseResult2D(
 38        Fixed64 normalVelocity,
 39        Fixed64 impulseScalar,
 40        Fixed64 appliedImpulseScalar,
 41        Vector2d linearVelocityDeltaA,
 42        Fixed64 angularVelocityDeltaA,
 43        Vector2d linearVelocityDeltaB,
 44        Fixed64 angularVelocityDeltaB,
 45        bool hasRepresentableNormalVelocity = true,
 46        bool hasRepresentableAppliedImpulse = true,
 47        bool hasRepresentableAccumulatedImpulse = true)
 48    {
 100949        NormalVelocity = normalVelocity;
 100950        ImpulseScalar = impulseScalar;
 100951        AppliedImpulseScalar = appliedImpulseScalar;
 100952        LinearVelocityDeltaA = linearVelocityDeltaA;
 100953        AngularVelocityDeltaA = angularVelocityDeltaA;
 100954        LinearVelocityDeltaB = linearVelocityDeltaB;
 100955        AngularVelocityDeltaB = angularVelocityDeltaB;
 100956        HasRepresentableNormalVelocity = hasRepresentableNormalVelocity;
 100957        HasRepresentableAppliedImpulse = hasRepresentableAppliedImpulse;
 100958        HasRepresentableAccumulatedImpulse =
 100959            hasRepresentableAccumulatedImpulse;
 100960    }
 61
 62    public Fixed64 NormalVelocity { get; }
 63
 64    public Fixed64 ImpulseScalar { get; }
 65
 66    public Fixed64 AppliedImpulseScalar { get; }
 67
 68    public bool HasRepresentableNormalVelocity { get; }
 69
 70    public bool HasRepresentableAppliedImpulse { get; }
 71
 72    public bool HasRepresentableAccumulatedImpulse { get; }
 73
 74    public Vector2d LinearVelocityDeltaA { get; }
 75
 76    public Fixed64 AngularVelocityDeltaA { get; }
 77
 78    public Vector2d LinearVelocityDeltaB { get; }
 79
 80    public Fixed64 AngularVelocityDeltaB { get; }
 81}
 82
 83/// <summary>
 84/// Side-effect-free 2D velocity deltas for a normal response whose impulse
 85/// scalar does not need to be representable.
 86/// </summary>
 87internal readonly struct ContactNormalVelocityDeltaResult2D
 88{
 89    public ContactNormalVelocityDeltaResult2D(
 90        Fixed64 normalVelocity,
 91        Vector2d linearVelocityDeltaA,
 92        Fixed64 angularVelocityDeltaA,
 93        Vector2d linearVelocityDeltaB,
 94        Fixed64 angularVelocityDeltaB)
 95        : this(
 96            normalVelocity,
 97            linearVelocityDeltaA,
 98            angularVelocityDeltaA,
 99            linearVelocityDeltaB,
 100            angularVelocityDeltaB,
 101            normalVelocity < Fixed64.Zero,
 102            hasRepresentableNormalVelocity: true)
 103    {
 104    }
 105
 106    public ContactNormalVelocityDeltaResult2D(
 107        Fixed64 normalVelocity,
 108        Vector2d linearVelocityDeltaA,
 109        Fixed64 angularVelocityDeltaA,
 110        Vector2d linearVelocityDeltaB,
 111        Fixed64 angularVelocityDeltaB,
 112        bool isClosing,
 113        bool hasRepresentableNormalVelocity)
 114    {
 115        NormalVelocity = normalVelocity;
 116        LinearVelocityDeltaA = linearVelocityDeltaA;
 117        AngularVelocityDeltaA = angularVelocityDeltaA;
 118        LinearVelocityDeltaB = linearVelocityDeltaB;
 119        AngularVelocityDeltaB = angularVelocityDeltaB;
 120        IsClosing = isClosing;
 121        HasRepresentableNormalVelocity = hasRepresentableNormalVelocity;
 122    }
 123
 124    public Fixed64 NormalVelocity { get; }
 125
 126    public bool IsClosing { get; }
 127
 128    public bool HasRepresentableNormalVelocity { get; }
 129
 130    public Vector2d LinearVelocityDeltaA { get; }
 131
 132    public Fixed64 AngularVelocityDeltaA { get; }
 133
 134    public Vector2d LinearVelocityDeltaB { get; }
 135
 136    public Fixed64 AngularVelocityDeltaB { get; }
 137}
 138
 139/// <summary>
 140/// Calculates allocation-free 2D contact-point normal response without mutating either body.
 141/// </summary>
 142internal static class ContactNormalImpulse2D
 143{
 144    internal static bool TryCalculateVelocityDeltas(
 145        SolidBody2D? bodyA,
 146        Vector2d linearVelocityA,
 147        Fixed64 angularVelocityA,
 148        Vector2d relativeContactPointA,
 149        SolidBody2D? bodyB,
 150        Vector2d linearVelocityB,
 151        Fixed64 angularVelocityB,
 152        Vector2d relativeContactPointB,
 153        Vector2d normal,
 154        Fixed64 restitution,
 155        Fixed64 restitutionVelocityThreshold,
 156        out ContactNormalVelocityDeltaResult2D result)
 157    {
 158        result = default;
 159        if (!TryComputeNormalVelocity(
 160                linearVelocityA,
 161                angularVelocityA,
 162                relativeContactPointA,
 163                linearVelocityB,
 164                angularVelocityB,
 165                relativeContactPointB,
 166                normal,
 167                out Fixed64 normalVelocity))
 168        {
 169            return false;
 170        }
 171        if (normalVelocity >= Fixed64.Zero)
 172        {
 173            result = ZeroVelocityDelta(normalVelocity);
 174            return true;
 175        }
 176
 177        if (!TryComputeDenominator(
 178                bodyA,
 179                relativeContactPointA,
 180                bodyB,
 181                relativeContactPointB,
 182                normal,
 183                out Fixed64 denominator)
 184            || denominator <= Fixed64.Zero)
 185        {
 186            return false;
 187        }
 188
 189        Fixed64 appliedRestitution = normalVelocity < -restitutionVelocityThreshold
 190            ? restitution
 191            : Fixed64.Zero;
 192        Fixed64 responseFactor = -(Fixed64.One + appliedRestitution);
 193        bool linearAResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 194                bodyA?.ProjectLinearMotion(-normal) ?? Vector2d.Zero,
 195                normalVelocity,
 196                responseFactor,
 197                bodyA?.EffectiveInverseMass ?? Fixed64.Zero,
 198                denominator,
 199                out Vector2d linearVelocityDeltaA);
 200        bool angularAResolved = TryResolveAngularVelocityDelta(
 201                bodyA,
 202                relativeContactPointA,
 203                -normal,
 204                normalVelocity,
 205                responseFactor,
 206                denominator,
 207                out Fixed64 angularVelocityDeltaA);
 208        bool linearBResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 209                bodyB?.ProjectLinearMotion(normal) ?? Vector2d.Zero,
 210                normalVelocity,
 211                responseFactor,
 212                bodyB?.EffectiveInverseMass ?? Fixed64.Zero,
 213                denominator,
 214                out Vector2d linearVelocityDeltaB);
 215        bool angularBResolved = TryResolveAngularVelocityDelta(
 216                bodyB,
 217                relativeContactPointB,
 218                normal,
 219                normalVelocity,
 220                responseFactor,
 221                denominator,
 222                out Fixed64 angularVelocityDeltaB);
 223        if (!(linearAResolved
 224                & angularAResolved
 225                & linearBResolved
 226                & angularBResolved))
 227        {
 228            return false;
 229        }
 230
 231        result = new ContactNormalVelocityDeltaResult2D(
 232            normalVelocity,
 233            linearVelocityDeltaA,
 234            angularVelocityDeltaA,
 235            linearVelocityDeltaB,
 236            angularVelocityDeltaB);
 237        return true;
 238    }
 239
 240    internal static bool TryCalculateAccumulatedDelta(
 241        SolidBody2D? bodyA,
 242        Vector2d linearVelocityA,
 243        Fixed64 angularVelocityA,
 244        Vector2d relativeContactPointA,
 245        SolidBody2D? bodyB,
 246        Vector2d linearVelocityB,
 247        Fixed64 angularVelocityB,
 248        Vector2d relativeContactPointB,
 249        Vector2d normal,
 250        Fixed64 restitution,
 251        Fixed64 restitutionVelocityThreshold,
 252        Fixed64 accumulatedImpulse,
 253        Fixed64 positiveImpulseScale,
 254        Fixed64 negativeImpulseScale,
 255        out ContactNormalImpulseResult2D result)
 256    {
 257        result = default;
 258        bool inputsValid =
 259            accumulatedImpulse >= Fixed64.Zero
 260            & positiveImpulseScale >= Fixed64.Zero
 261            & negativeImpulseScale >= Fixed64.Zero;
 262        if (!inputsValid)
 263            return false;
 264        if (!TryComputeNormalVelocity(
 265                linearVelocityA,
 266                angularVelocityA,
 267                relativeContactPointA,
 268                linearVelocityB,
 269                angularVelocityB,
 270                relativeContactPointB,
 271                normal,
 272                out Fixed64 normalVelocity)
 273            || !TryComputeDenominator(
 274                bodyA,
 275                relativeContactPointA,
 276                bodyB,
 277                relativeContactPointB,
 278                normal,
 279                out Fixed64 denominator))
 280        {
 281            return false;
 282        }
 283        if (denominator <= Fixed64.Zero)
 284        {
 285            result = Zero(normalVelocity);
 286            return true;
 287        }
 288
 289        Fixed64 appliedRestitution = normalVelocity < -restitutionVelocityThreshold
 290            ? restitution
 291            : Fixed64.Zero;
 292        Fixed64 responseFactor = -(Fixed64.One + appliedRestitution);
 293        Fixed64 impulseScale = normalVelocity < Fixed64.Zero
 294            ? positiveImpulseScale
 295            : negativeImpulseScale;
 296        Fixed64 impulseScalar;
 297        if (!Fixed64.TryMultiplyDivide(
 298                normalVelocity,
 299                responseFactor,
 300                impulseScale,
 301                denominator,
 302                out Fixed64 scaledImpulse))
 303        {
 304            if (normalVelocity < Fixed64.Zero)
 305                return false;
 306            impulseScalar = -accumulatedImpulse;
 307        }
 308        else if (!Fixed64.TryAdd(
 309                    accumulatedImpulse,
 310                    scaledImpulse,
 311                    out Fixed64 accumulated)
 312                || !Fixed64.TrySubtract(
 313                    FixedMath.Max(Fixed64.Zero, accumulated),
 314                    accumulatedImpulse,
 315                    out impulseScalar))
 316        {
 317            return false;
 318        }
 319        if (impulseScalar == Fixed64.Zero)
 320        {
 321            result = Zero(normalVelocity);
 322            return true;
 323        }
 324
 325        bool linearAResolved = TryComputeLinearVelocityDelta(
 326            bodyA,
 327            -normal,
 328            impulseScalar,
 329            out Vector2d linearA);
 330        bool angularAResolved = TryComputeAngularVelocityDelta(
 331            bodyA,
 332            relativeContactPointA,
 333            -normal,
 334            impulseScalar,
 335            out Fixed64 angularA);
 336        bool linearBResolved = TryComputeLinearVelocityDelta(
 337            bodyB,
 338            normal,
 339            impulseScalar,
 340            out Vector2d linearB);
 341        bool angularBResolved = TryComputeAngularVelocityDelta(
 342            bodyB,
 343            relativeContactPointB,
 344            normal,
 345            impulseScalar,
 346            out Fixed64 angularB);
 347        if (!(linearAResolved
 348            & angularAResolved
 349            & linearBResolved
 350            & angularBResolved))
 351        {
 352            return false;
 353        }
 354        result = new ContactNormalImpulseResult2D(
 355            normalVelocity,
 356            impulseScalar,
 357            linearA,
 358            angularA,
 359            linearB,
 360            angularB);
 361        return true;
 362    }
 363
 364    internal static bool TryCalculateVelocityDeltasExact(
 365        SolidBody2D? bodyA,
 366        Vector2d linearVelocityA,
 367        Fixed64 angularVelocityA,
 368        in ExactLever3D relativeContactPointA,
 369        SolidBody2D? bodyB,
 370        Vector2d linearVelocityB,
 371        Fixed64 angularVelocityB,
 372        in ExactLever3D relativeContactPointB,
 373        Vector2d normal,
 374        Fixed64 restitution,
 375        Fixed64 restitutionVelocityThreshold,
 376        out ContactNormalVelocityDeltaResult2D result)
 377    {
 378        result = default;
 379        if (!ExactContactLever2D.TryGetNormalResponse(
 380                bodyA,
 381                linearVelocityA,
 382                angularVelocityA,
 383                relativeContactPointA,
 384                bodyB,
 385                linearVelocityB,
 386                angularVelocityB,
 387                relativeContactPointB,
 388                normal,
 389                restitution,
 390                restitutionVelocityThreshold,
 391                out ExactNormalResponse3D response))
 392        {
 393            return false;
 394        }
 395
 396        bool hasNormalVelocity =
 397            response.TryGetNormalVelocity(out Fixed64 normalVelocity);
 398        result = new ContactNormalVelocityDeltaResult2D(
 399            normalVelocity,
 400            ExactContactLever2D.ToPlanar(response.FirstLinearVelocityDelta),
 401            ExactContactLever2D.ToPlanarAngular(response.FirstAngularVelocityDelta),
 402            ExactContactLever2D.ToPlanar(response.SecondLinearVelocityDelta),
 403            ExactContactLever2D.ToPlanarAngular(response.SecondAngularVelocityDelta),
 404            response.IsClosing,
 405            hasNormalVelocity);
 406        return true;
 407    }
 408
 409    internal static bool TryCalculateAccumulatedDeltaExact(
 410        SolidBody2D? bodyA,
 411        Vector2d linearVelocityA,
 412        Fixed64 angularVelocityA,
 413        in ExactLever3D relativeContactPointA,
 414        SolidBody2D? bodyB,
 415        Vector2d linearVelocityB,
 416        Fixed64 angularVelocityB,
 417        in ExactLever3D relativeContactPointB,
 418        Vector2d normal,
 419        Fixed64 restitution,
 420        Fixed64 restitutionVelocityThreshold,
 421        Fixed64 accumulatedImpulse,
 422        Fixed64 positiveImpulseScale,
 423        Fixed64 negativeImpulseScale,
 424        out ContactNormalImpulseResult2D result)
 425    {
 426        result = default;
 427        if (!ExactContactLever2D.TryGetAccumulatedNormalResponse(
 428                bodyA,
 429                linearVelocityA,
 430                angularVelocityA,
 431                relativeContactPointA,
 432                bodyB,
 433                linearVelocityB,
 434                angularVelocityB,
 435                relativeContactPointB,
 436                normal,
 437                restitution,
 438                restitutionVelocityThreshold,
 439                accumulatedImpulse,
 440                positiveImpulseScale,
 441                negativeImpulseScale,
 442                out ExactNormalResponse3D response))
 443        {
 444            return false;
 445        }
 446
 447        bool hasNormalVelocity =
 448            response.TryGetNormalVelocity(out Fixed64 normalVelocity);
 449        bool hasAppliedImpulse =
 450            response.TryGetAppliedImpulse(out Fixed64 appliedImpulse);
 451        bool hasAccumulatedImpulse =
 452            response.TryGetAccumulatedImpulse(
 453                out Fixed64 newAccumulatedImpulse);
 454        Fixed64 impulseScalar = hasAccumulatedImpulse
 455            ? newAccumulatedImpulse - accumulatedImpulse
 456            : -accumulatedImpulse;
 457        result = new ContactNormalImpulseResult2D(
 458            normalVelocity,
 459            impulseScalar,
 460            appliedImpulse,
 461            ExactContactLever2D.ToPlanar(response.FirstLinearVelocityDelta),
 462            ExactContactLever2D.ToPlanarAngular(response.FirstAngularVelocityDelta),
 463            ExactContactLever2D.ToPlanar(response.SecondLinearVelocityDelta),
 464            ExactContactLever2D.ToPlanarAngular(response.SecondAngularVelocityDelta),
 465            hasNormalVelocity,
 466            hasAppliedImpulse,
 467            hasAccumulatedImpulse);
 468        return true;
 469    }
 470
 471    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 472    private static bool TryComputeNormalVelocity(
 473        Vector2d linearVelocityA,
 474        Fixed64 angularVelocityA,
 475        Vector2d relativeContactPointA,
 476        Vector2d linearVelocityB,
 477        Fixed64 angularVelocityB,
 478        Vector2d relativeContactPointB,
 479        Vector2d normal,
 480        out Fixed64 normalVelocity)
 481    {
 482        bool angularAResolved = ContactResponseArithmetic3D.TryCross(
 483            new Vector3d(
 484                Fixed64.Zero,
 485                -angularVelocityA,
 486                Fixed64.Zero),
 487            ExactContactLever2D.ToSpatial(relativeContactPointA),
 488            out Vector3d angularA);
 489        bool angularBResolved = ContactResponseArithmetic3D.TryCross(
 490            new Vector3d(
 491                Fixed64.Zero,
 492                -angularVelocityB,
 493                Fixed64.Zero),
 494            ExactContactLever2D.ToSpatial(relativeContactPointB),
 495            out Vector3d angularB);
 496        bool relativeResolved = Vector3d.TrySubtractSums(
 497            ExactContactLever2D.ToSpatial(linearVelocityB),
 498            angularB,
 499            ExactContactLever2D.ToSpatial(linearVelocityA),
 500            angularA,
 501            out Vector3d relative);
 502        bool projectionResolved = ContactResponseArithmetic3D.TryDot(
 503            relative,
 504            ExactContactLever2D.ToSpatial(normal),
 505            out normalVelocity);
 506        if (!(angularAResolved
 507            & angularBResolved
 508            & relativeResolved
 509            & projectionResolved))
 510        {
 511            normalVelocity = default;
 512            return false;
 513        }
 514        return true;
 515    }
 516
 517    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 518    internal static bool TryComputeDenominator(
 519        SolidBody2D? bodyA,
 520        Vector2d relativeContactPointA,
 521        SolidBody2D? bodyB,
 522        Vector2d relativeContactPointB,
 523        Vector2d normal,
 524        out Fixed64 denominator)
 525    {
 526        bool angularAResolved = TryComputeAngularDenominator(
 527            bodyA,
 528            relativeContactPointA,
 529            normal,
 530            out Fixed64 angularA);
 531        bool angularBResolved = TryComputeAngularDenominator(
 532            bodyB,
 533            relativeContactPointB,
 534            normal,
 535            out Fixed64 angularB);
 536        bool linearAResolved = TryGetConstrainedInverseMass(
 537            bodyA,
 538            normal,
 539            out Fixed64 linearA);
 540        bool linearBResolved = TryGetConstrainedInverseMass(
 541            bodyB,
 542            normal,
 543            out Fixed64 linearB);
 544        bool sumResolved = Fixed64.TryAdd(
 545                linearA,
 546                linearB,
 547                out Fixed64 linear)
 548            & Fixed64.TryAdd(
 549                linear,
 550                angularA,
 551                out Fixed64 first)
 552            & Fixed64.TryAdd(
 553                first,
 554                angularB,
 555                out denominator);
 556        if (!(linearAResolved
 557            & linearBResolved
 558            & angularAResolved
 559            & angularBResolved
 560            & sumResolved))
 561        {
 562            denominator = default;
 563            return false;
 564        }
 565        return true;
 566    }
 567
 568    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 569    internal static bool TryComputeAngularDenominator(
 570        SolidBody2D? body,
 571        Vector2d relativeContactPoint,
 572        Vector2d axis,
 573        out Fixed64 denominator)
 574    {
 575        if (body?.CanRotate != true)
 576        {
 577            denominator = Fixed64.Zero;
 578            return true;
 579        }
 580
 581        denominator = default;
 582        bool crossResolved = ContactResponseArithmetic3D.TryCross(
 583                ExactContactLever2D.ToSpatial(relativeContactPoint),
 584                ExactContactLever2D.ToSpatial(axis),
 585                out Vector3d cross);
 586        bool denominatorResolved = crossResolved
 587            && Fixed64.TryMultiplyDivide(
 588                cross.Y,
 589                cross.Y,
 590                body.EffectiveInverseMomentOfInertia,
 591                Fixed64.One,
 592                out denominator);
 593        if (!denominatorResolved
 594            || (denominator == Fixed64.Zero
 595                && cross.Y != Fixed64.Zero
 596                && body.EffectiveInverseMomentOfInertia
 597                    != Fixed64.Zero))
 598        {
 599            denominator = default;
 600            return false;
 601        }
 602
 603        return true;
 604    }
 605
 606    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 607    internal static bool TryGetConstrainedInverseMass(
 608        SolidBody2D? body,
 609        Vector2d axis,
 610        out Fixed64 inverseMass)
 611    {
 612        if (body == null)
 613        {
 614            inverseMass = Fixed64.Zero;
 615            return true;
 616        }
 617
 618        inverseMass = body.GetConstrainedInverseMass(axis);
 619        return inverseMass != Fixed64.Zero
 620            || body.EffectiveInverseMass == Fixed64.Zero
 621            || body.ProjectLinearMotion(axis) == Vector2d.Zero;
 622    }
 623
 624    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 625    internal static bool TryComputeLinearVelocityDelta(
 626        SolidBody2D? body,
 627        Vector2d signedNormal,
 628        Fixed64 impulseScalar,
 629        out Vector2d velocityDelta) =>
 630        ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 631            body?.ProjectLinearMotion(signedNormal) ?? Vector2d.Zero,
 632            impulseScalar,
 633            body?.EffectiveInverseMass ?? Fixed64.Zero,
 634            Fixed64.One,
 635            out velocityDelta);
 636
 637    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 638    internal static bool TryComputeAngularVelocityDelta(
 639        SolidBody2D? body,
 640        Vector2d relativeContactPoint,
 641        Vector2d signedNormal,
 642        Fixed64 impulseScalar,
 643        out Fixed64 velocityDelta)
 644    {
 645        velocityDelta = Fixed64.Zero;
 646        if (body?.CanRotate != true)
 647            return true;
 648
 649        Vector3d spatialPoint =
 650            ExactContactLever2D.ToSpatial(relativeContactPoint);
 651        Vector3d spatialNormal =
 652            ExactContactLever2D.ToSpatial(signedNormal);
 653        return ContactResponseArithmetic3D.TryCross(
 654                spatialPoint,
 655                spatialNormal,
 656                out Vector3d cross)
 657            && Fixed64.TryMultiplyDivide(
 658                -cross.Y,
 659                impulseScalar,
 660                body.EffectiveInverseMomentOfInertia,
 661                Fixed64.One,
 662                out velocityDelta);
 663    }
 664
 665    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 666    private static bool TryResolveAngularVelocityDelta(
 667        SolidBody2D? body,
 668        Vector2d relativeContactPoint,
 669        Vector2d signedNormal,
 670        Fixed64 normalVelocity,
 671        Fixed64 responseFactor,
 672        Fixed64 denominator,
 673        out Fixed64 velocityDelta)
 674    {
 675        velocityDelta = Fixed64.Zero;
 676        if (body?.CanRotate != true)
 677            return true;
 678
 679        Fixed64 torqueScale = Vector2d.CrossProduct(relativeContactPoint, signedNormal);
 680        if (torqueScale == Fixed64.Zero)
 681            return true;
 682
 683        bool angularScaleResolved = Fixed64.TryMultiplyDivide(
 684            normalVelocity,
 685            responseFactor,
 686            body.EffectiveInverseMomentOfInertia,
 687            denominator,
 688            out Fixed64 angularScale);
 689        bool velocityDeltaResolved = Fixed64.TryMultiplyDivide(
 690            torqueScale,
 691            angularScale,
 692            Fixed64.One,
 693            out velocityDelta);
 694        return angularScaleResolved
 695            & (angularScale != Fixed64.Zero | torqueScale.Abs() <= Fixed64.One)
 696            & velocityDeltaResolved;
 697    }
 698
 699    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 700    private static ContactNormalImpulseResult2D Zero(Fixed64 normalVelocity) =>
 701        new(
 702            normalVelocity,
 703            Fixed64.Zero,
 704            Vector2d.Zero,
 705            Fixed64.Zero,
 706            Vector2d.Zero,
 707            Fixed64.Zero);
 708
 709    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 710    private static ContactNormalVelocityDeltaResult2D ZeroVelocityDelta(Fixed64 normalVelocity) =>
 711        new(
 712            normalVelocity,
 713            Vector2d.Zero,
 714            Fixed64.Zero,
 715            Vector2d.Zero,
 716            Fixed64.Zero);
 717}